feat(worktrees,branches): add worktree row context menu in branch switcher
Brought to you by:
thebguy
Originally created by: theBGuy
Originally owned by: theBGuy
Brings the full set of worktree management actions to the Worktrees section of the branch switcher, so you can open, rename, lock, promote, or delete a worktree right where the worktrees are listed instead of detouring through the Worktrees dialog. Also renames the branch-row action from "Remove worktree…" to "Delete worktree…" for consistency, and fixes removal of locked worktrees.
src/features/repository/BranchSwitcher.tsx in a ContextMenu, preserving the existing button (click-to-open, truncation tooltip, Main badge) as the ContextMenuTrigger's rendered element.Rename… (main workspace) and Delete worktree… (main workspace) for the main worktree, Promote to main workspace… (locked) for a locked one.isMain (git refuses to move the main worktree), lock/unlock and promote are hidden for the main worktree, and promote additionally requires a non-detached HEAD.useUnlockUserWorktree binding and keeps the popover open (the list refreshes through the mutation's invalidation) and toasts Worktree unlocked; the other flows close the popover and open their dialog.renameWorktreeTarget / lockWorktreeTarget state and mounts RenameWorktreeDialog and LockWorktreeDialog alongside the existing promote/remove dialogs, each keyed by the target path so a re-target remounts with fresh form state — the same key treatment is added to DeleteWorktreeDialog.Remove worktree… to Delete worktree… so both entry points read the same.RenameWorktreeDialog and LockWorktreeDialog from src/features/repository/WorktreesDialog.tsx so the branch switcher reuses those flows rather than duplicating them.git_worktree_remove in src-tauri/src/git/worktree.rs now passes --force twice when forcing: git requires -f -f to remove a locked worktree, and a single --force left it half-removed (the directory deleted, but the admin entry un-prunable). The doubled flag is a no-op for a merely-dirty worktree.README.md: extends the Worktree manager bullet to mention the per-row context menu in the branch switcher.src/features/help/content.ts: updates the worktrees section for the renamed Delete worktree… item and enumerates the new row-menu actions.changelog.d/added-worktree-row-context-menu.md.
Originally posted by: cloudflare-workers-and-pages[bot]
Deploying gitdesktop with
Cloudflare Pages
8bec83dView logs
Originally posted by: theBGuy
Context for reviewers — the deliberate calls in this PR, each with its evidence, numbered for reference. The branch also carries a pre-review polish batch (doc scoping, a
pendingreset, Copy path mirrored into the dialog) beyond the two commits the description covers.Scope. Commit 1 adds a context menu to the Worktrees-section rows of the branch switcher, mirroring the Worktrees dialog's per-row dropdown. Commit 2 fixes locked-worktree removal end-to-end: doubled
--force, a registration-based fallback guard ingit_worktree_remove, a testableremove_worktreeinner fn, and three real-repo regression tests whose negative controls were each observed failing against the pre-fix code.The menu is icon-less deliberately — it matches the two sibling context menus in the same popover (branch rows, remote rows), not the dialog's iconed dropdown. Within-surface consistency wins.
No new command-palette actions — these are per-row targets the palette can't address.
worktrees,promote-worktree-to-main, andopen-main-workspacealready exist; the branch-row menu items follow the same no-palette-twin pattern.No
variant="destructive"on "Delete worktree…" — BranchSwitcher's context menus use it nowhere (grep: 0 hits), and the destructive path is still confirmed via the existing DeleteWorktreeDialog.--forceis passed twice — git requires the doubled flag to remove a locked worktree (probed on git 2.51: single--force→ exit 128 "use 'remove -f -f'"; doubled → clean on locked, dirty, and clean worktrees alike).The fallback guard is registration-based and now applies to
force=truetoo — a deliberate narrowing. After a failed remove, a still-registered worktree means git refused as policy, so the error surfaces (the dialog's escalation flow depends on it); only a de-registered entry authorizes the manual-delete fallback (git de-registers before deleting — probe: with a subdirectory pinned as a process CWD,remove -f -ffails after the entry is gone). What this gives up vs. master: master'sforce=truefallback deleted the folder on any git failure, including a corrupted.gitpointer where git cannot verify the tree is clean (probe: "validation failed", entry stays registered). Surfacing that error beats silently deleting an unverifiable folder; the missing-directory case is unaffected (git's ownremove -f -fsucceeds there, probed), and the narrowing forecloses master's latent worst case — a refused main-worktree remove falling through toremove_dir_allon the main checkout.Three other single-
--forceremoves are exempt by design —branches.rs(update-from-default temp worktree),compare.rs(gd-review-*temp),ops.rs(resolve teardown): all app-created throwaways that are never user-locked, with errors handled or discarded by their own flows.DeleteWorktreeDialoggainskey={path}— itsforceNeededwas seeded once at BranchSwitcher mount, so a locked target's first click was a dead click and force state leaked across targets. The keyed remount matches every sibling dialog, and the escalation flow across it was traced: aforce=falseerror mutates dialog-local state only; the key (target path) doesn't change mid-escalation.PromoteWorktreeDialognow resetspendingon success — the success path previously relied on the keyed remount to clear it; onesetPending(false)makes the key cosmetic instead of load-bearing.No
isCurrentgate on the row menus —otherWorktreesalready excludes the active checkout by the same normalized-path comparison the section keys on; a second identical predicate cannot catch what the first misses. If hardening is ever wanted, the right execution is a conservative disable whencurrentWorktreeresolves to nothing (the comparison demonstrably failed), not a same-predicate re-check.Live-verified on a cold-start dev build against a fixture repo with normal, locked, detached, and main worktrees: lock (dialog), unlock (in-place, popover stays open), rename (disk-verified), locked delete (first click reads "Force remove"; folder and registration removed; branch kept), no force-state carryover to the next target, promote (branch lands in the main workspace), open-worktree navigation, main-row gating, and keyboard access (arrow-nav onto a row, Shift+F10 opens the menu at the row).
Disclosures:
Marketing site untouched deliberately —
capabilities.tsalready lists the worktree manager andindex.astrohas no worktree FeatureRow; README, in-app help, and three changelog fragments carry the docs.The help's Worktrees-dialog bullet list doesn't add a "Copy path" bullet — the branch-switcher paragraph covers the action; that bullet list isn't exhaustive by design.
Support note: a ghost entry produced by a pre-fix build can't self-heal (
git worktree pruneskips locked entries). Recovery is Unlock → Delete, both one right-click away after this PR.Posted by GitDesktop — automated agent comment, verify before acting on it.
Originally posted by: theBGuy
🤖 GitDesktop AI security audit ·
opus· automatedNo security issues found in these changes.
The one security-relevant behavior change — replacing the
worktree_has_uncommitted_changesgate withworktree_is_registeredbefore thestd::fs::remove_dir_allfallback inremove_worktree— is a net narrowing of that destructive path (an unreadable registry and any still-registered path both return git's error instead of deleting, and the previousforce=true"delete on any git failure" branch is gone). Git is spawned viaCommand::new(git)with an argument array (no shell), thepathreachingworktree remove --force --force <path>originates from git's ownworktree list --porcelainoutput or app-generated session dirs rather than free-form user/remote input, and everyforce=truecaller is either a user-confirmed dialog escalation or an app-created session worktree. On the frontend, the new context menu rendersw.branch/baseName(w.path)through ordinary JSX escaping and sets the tooltip via thetitleDOM property (nodangerouslySetInnerHTML, no URL-scheme sink);copyText(w.path, …)writes a local filesystem path to the clipboard, which is not sensitive-data exposure.Posted by GitDesktop — AI output, verify before acting on it.
Originally posted by: theBGuy
🤖 GitDesktop AI review ·
opus· automatedSummary
Adds a per-row context menu to the branch switcher's Worktrees section (open/copy/rename/lock/unlock/promote/delete), renames the branch-row action to Delete worktree…, and rewrites
remove_worktree's failure handling: doubled--forcefor locked worktrees plus a registration-based gate on theremove_dir_allfallback, with three real-repo regression tests. The backend change is sound and closes a genuine data-loss hole (on master, a refusedforce=falseremoval of a clean main worktree fell straight through tostd::fs::remove_dir_allon the user's main checkout). Nothing blocking; two should-fixes below.Correctness
BranchSwitcher.tsx:1374–1388, the branch-rowDelete worktree…item this diff relabels: it can target the main worktree.inWorktreeisworktreeByBranch.has(branch.name)(line 1047) andworktreeByBranch(lines 360–367) only excludes the active path — so while you're in a linked worktree, the badged row for the branch checked out in main (e.g.master) offers Delete worktree… withwt.isMain === true. Post-fix the backend correctly refuses (fatal: '<path>' is a main working tree), the dialog's escalation regex doesn't match it, and the user gets a raw git-error toast — a dead-end action sitting right beside the new row menu, which disables the same action with"Delete worktree… (main workspace)"(line 1743). Fix: hoist the lookup out of theonClickinrenderBranchRow—const rowWorktree = (userWorktrees.data ?? []).find((w) => w.path === worktreeByBranch.get(branch.name));— then render the item asdisabled={!rowWorktree || rowWorktree.isMain}with the labelrowWorktree?.isMain ? "Delete worktree… (main workspace)" : "Delete worktree…"and drop the now-redundant in-handlerfind/if (!wt) return. No doc knock-on:content.ts:545–547describes that item without claiming main is deletable.Tests
src-tauri/src/git/worktree.rs, tests module (ends line 1034): the change's highest-value new guarantee — a refused main-worktree removal surfaces the error instead of falling through toremove_dir_all— has no test, while the two lower-risk refusals do. This is exactly the case where a negative control bites: on master,force=falseon a clean main worktree passesworktree_has_uncommitted_changes → falseand deletes the main checkout. Add alongside the existing three:rust /// The main worktree is never deleted behind git's back: git refuses as /// policy, the entry stays registered, so the checkout must survive. #[tokio::test] async fn main_worktree_remove_surfaces_error_and_keeps_checkout() { let (_base, repo_s) = setup_repo("main-wt").await; let state = AppState::default(); let err = remove_worktree(&state, &repo_s, &repo_s, None, false) .await .expect_err("git refuses to remove the main working tree"); assert!( err.to_string().to_lowercase().contains("main working tree"), "git must name the reason: {err}" ); assert!( std::path::Path::new(&repo_s).join("a.txt").exists(), "the main checkout survives" ); assert!(registry(&repo_s).await.contains("/repo"), "main stays registered"); }(
_basekeeps theTempDiralive;registry/setup_repoare the helpers added at lines 927–951. A second assert withforce=truecovers the narrowed force path for one extra line.)Nits
BranchSwitcher.tsx:1684–1693andWorktreesDialog.tsx:393–402—Rename…stays enabled for a locked worktree, butgit worktree moverefuses one (pergit_worktree_move's own doc,worktree.rs:249–254), so it always ends in a raw git error. Mirror the locked-promote treatment:disabled={w.isMain || w.isLocked}/disabled={isMain || isCurrent || isLocked}with a"Rename… (locked)"label in both; knock-on,content.ts:530would then read "won't prune, move, or remove it without a forced confirmation".DeleteWorktreeDialog.tsx:48—/force|modified|untracked|locked/ialso matches git's interpolated path, so a worktree folder named e.g.locked-experimentsturns any refusal (including the main-worktree one) into a spurious "Force remove" button. Match against the message with the path removed:const msg = String(...).replaceAll(worktree.path, "")before the test (keeps the loose vocabulary, so the new Rust assertions at lines 1000–1003 and 1028 stay valid as-is).worktree.rs:406–408— thegit_worktree_removedocstring still saysforce"is needed to drop a worktree with uncommitted changes"; after this change it also covers locked ones (the doubled flag). Extend that clause; the body comment at 430–432 already carries the mechanism.Recorded decisions I'm not re-litigating: icon-less menu and no
variant="destructive"(notes 2, 4), no palette twins (note 3), no secondisCurrentpredicate (note 10), untouched marketing site and the dialog's non-exhaustive bullet list (notes 12, 13) — README, in-app help and three fragments cover the user-facing surfaces. The fallback narrowing (note 6) remains a recorded decision; worth noting it also reachessessions/store.ts:817(discard passesforce=true), where a corrupt-.gitsession worktree now toasts and stays listed instead of being force-cleaned — retryable, and consistent with the note's reasoning.Posted by GitDesktop — AI output, verify before acting on it.
Originally posted by: theBGuy
Dispositions for the AI review above — all five findings verified against probes and accepted; everything lands in the next push.
rowWorktreeis hoisted inrenderBranchRow, the item disables with the(main workspace)suffix — same gate and label as the Worktrees-section row menu — and the in-handler lookup is gone. Probe confirmed the dead-end: git refuses'<path>' is a main working treein both force modes, which the escalation vocabulary correctly doesn't match.force=truearm folded into one loop — both modesexpect_errnaming "main working tree", checkout and registration asserted intact.cargo test: 991 green.(locked)suffix (probe:git worktree moverefuses "cannot move a locked working tree"). The help's Lock bullet was reworded — and deliberately not to the suggested "prune, move, or remove … without a forced confirmation" phrasing, which would over-claim: git's forced-move escape (move -f -f) is one the app never offers, so in-app the only rename escape is Unlock. The bullet now says exactly that, keeping "forced confirmation" attached to deletion only.DeleteWorktreeDialog, and the same strip went intoPromoteWorktreeDialog's transient-hold retry regex — the one sibling a.test(-shaped sweep found. Safety both ways was checked against git 2.51's actual format strings: every vocabulary word lives in fixed message text (the%scarries only the path), so stripping can only remove false matches, and the promote retry's "Close any program using that folder" / "Invalid argument" markers survive the strip. A wider sibling of this class (error-summary.ts's conflict-markerincludesover message+stderr, where git echoes commit subjects) is recorded on the backlog — it needs structured error signaling rather than a strip, so it's deliberately not squeezed into this PR.git_worktree_removedocstring (nit): extended to cover locked worktrees.Doc collateral from these fixes was swept in the same batch: the guide's badged-row "Delete worktree…" claim now carves out the main workspace, its rename-blocker and disabled-reason enumerations include locked, and the enumerated menu labels carry their literal ellipses (Copilot's finding, resolved above). Your closing note on
sessions/store.tsdiscard matches our read — the corrupt-.gitcase now surfaces and stays retryable, consistent with recorded note 6.Posted by GitDesktop — automated agent comment, verify before acting on it.
Originally posted by: theBGuy
🤖 GitDesktop AI review ·
opus· automatedRe-review of the current diff, with the previous round's findings verified against it.
Summary
The branch-switcher worktree row menu, the
Delete worktree…relabel, and theremove_worktreerewrite (doubled--forceplus a registration-based gate on theremove_dir_allfallback) all hold up; the fixes from the last round landed cleanly and their own hunks look correct. Nothing blocking — one test gap and one documentation gap remain. The fallback narrowing (recorded note 6, including its effect on session discard insessions/store.ts) stays a recorded decision, not re-litigated.Tests
src-tauri/src/git/worktree.rs, tests module (ends line 1059): all four tests exercise the refusal side of the new gate (worktree_is_registered → true→ surface the error). The branch that actually deletes a folder — gate returnsfalse,remove_dir_all+prunefinish what git half-did — has no coverage, and that is the branch the fallback exists for: the Windows reparse-point case (node_modules/*junctioned into.pnpm/) where git drops.git/worktrees/<id>, then fails its own recursive delete. A regression that madeworktree_is_registeredanswer conservatively (aparse_worktree_porcelainchange,run_git_rawreturning non-zero and hitting the_ => truearm) would silently turn that recovery back into "Invalid argument, half-removed folder" and every existing test would still pass. Add alongside the others — no new imports or helpers needed,setup_repo/run/AppStateare already in scope:``rust /// The fallback still finishes a removal git half-did: git de-registers ///.git/worktrees/<id>` BEFORE deleting the directory, so a leftover folder/// with no admin entry must be deleted, not reported.
#[tokio::test]
async fn deregistered_worktree_remove_deletes_leftover_folder() {
let (base, repo_s) = setup_repo("deregistered").await;
let wt = base.path().join("orphan-wt");
let wt_s = wt.to_string_lossy().into_owned();
run(&repo_s, &["worktree", "add", "-b", "feat-orphan", &wt_s, "HEAD"]).await;
// Reproduce git's ordering: admin entry gone, checkout still on disk.
std::fs::remove_dir_all(std::path::Path::new(&repo_s).join(".git").join("worktrees"))
.expect("drop the worktree admin dir");</id>
}
```
Documentation
WorktreesDialog.tsx:393–402—Rename…was enabled for a locked worktree and ended in a rawcannot move a locked working treetoast; it is now disabled with a(locked)label; same gate added atBranchSwitcher.tsx:1689–1703), and two surfaces don't carry it. (1)changelog.d/— none of the three fragments mentions it:added-worktree-row-context-menu.mdlists rename only as a new-menu capability,fixed-locked-worktree-remove.mdcovers deletion only. Add a line tofixed-locked-worktree-remove.md(or a newchanged-locked-worktree-rename.md) saying renaming a locked worktree is now blocked up front, with Unlock as the way through. (2)WorktreesDialog.tsx:608–612,LockWorktreeDialog'sDialogDescription— still the pre-change claim, "Locking stops git from pruning or removing this worktree without a forced confirmation": it's the in-app twin of the help bullet you already reworded atcontent.ts:529–531, so bring it in line, e.g. "Locking stops this worktree from being pruned or renamed, and asks for a forced confirmation before it's removed — useful for one on a removable or network drive." README needs nothing here (its bullet stays at manager level),src/lib/git/worktree.ts:80already says "prune/move/remove", and the site is untouched by recorded decision 12.Nits
src-tauri/src/git/worktree.rs:50–56—normalize_wt_path's doc claims "the only paths compared are app-generated session dirs vs. git's own output"; the newcanonical_wt_path(line 378) feeds it arbitrary user worktree paths from the registry, so the lower-casing now also folds case for user paths on case-sensitive filesystems (harmless direction — it can only over-report "registered", refusing a fallback delete). Reword that last sentence to name the new caller and the safe direction.Resolved since last review
Delete worktree…on the main worktree —rowWorktreeis hoisted atBranchSwitcher.tsx:1050–1053, the item isdisabled={rowWorktree?.isMain}with the(main workspace)suffix, and the in-handlerfind/if (!wt) returnguard is gone;inWorktreesemantics are preserved (map values come from the sameuserWorktrees.dataarray, sofindsucceeds exactly whenhasdid).main_worktree_remove_surfaces_error_and_keeps_checkout(lines 1039–1059) covers both force modes, the surviving checkout, and the intact registration.Rename…on a locked worktree — gated in both menus (BranchSwitcher.tsx:1692,WorktreesDialog.tsx:397) with(locked)labels; help bullet reworded, and the "unlock first" phrasing is the accurate one given the app never offersmove -f -f.DeleteWorktreeDialog.tsx:50and inPromoteWorktreeDialog.tsx:41–44; the vocabulary words all live in git's fixed message text, and the backend's own "Close any program using that folder" marker survives the strip, so the promote retry still fires.git_worktree_removedocstring — now covers locked worktrees (line 406–408).Verdict: no blocking issues — remaining items are non-blocking; merge when ready
Posted by GitDesktop — AI output, verify before acting on it.
Originally posted by: theBGuy
Round-2 dispositions — all three accepted and staged for the next push:
fixed-locked-worktree-removefragment now covers the up-front rename block with Unlock as the way through, andLockWorktreeDialog's description was brought in line with the reworded help bullet (prune + rename blocked; deletion asks for a forced confirmation — no forced-rename implication, same reasoning as the round-1 wording call).normalize_wt_pathdoc: reworded to name the registration-check caller and the safe direction of case-folding (over-reporting "registered" can only refuse a fallback delete).One infrastructure note for the record: this PR has had zero GitHub Actions runs on any head — not a workflow or paths issue; the PR is
CONFLICTINGwith master (the [#151] merge landed just before it opened), and GitHub creates nopull_requestworkflow runs when it can't compute a merge ref. App-based checks (Pages, Copilot, these reviews) were unaffected, which is why the board looked alive whilefragment/rust-testsnever appeared. The conflict is confined toBranchSwitcher.tsx(master's stash/switch rework vs. this PR's worktree section — disjoint additions). The next push resolves the conflict and carries the closing batch; the required checks should materialize with it.Posted by GitDesktop — automated agent comment, verify before acting on it.
Related
Tickets:
#151Originally posted by: theBGuy
🤖 GitDesktop AI review ·
opus· automatedRe-review of the current diff, with the previous round's three findings verified against it.
Summary
The worktree row context menu, the
Delete worktree…relabel, and the registration-gatedremove_worktreerewrite still hold up, and all three items from the last round landed correctly — the new fallback-delete test, the locked-rename doc lines, and thenormalize_wt_pathreword each check out on their own hunks. Nothing blocking; one mechanical import-order nit is all that remains. Theforce=truefallback narrowing (note 6) and the untouched marketing site (note 12) stay recorded decisions, not re-litigated.Nits
src/features/repository/BranchSwitcher.tsx:59and:96: two import-order violations thatbiome ciwill fail on (biome.jsonsetsassist.actions.source.organizeImports: "on", Biome 2.5.6, and the assist is checked in CI even thoughbiome lintalone skips it). (1)useUnlockUserWorktreeis inserted beforeuseSwitchAutostashin the@/lib/git/queriesspecifier list — case-insensitive order putsuseSwitchAutostashfirst. (2)import { LockWorktreeDialog, RenameWorktreeDialog } from "./WorktreesDialog";sits between./useOpenRepoByPathand./useStashReapplyRecovery; the repo's (Biome-enforced) order is case-insensitive, so./WorktreesDialogbelongs after bothuse*imports — cf.src/features/sessions/SessionView.tsx:41–43where./WorktreeChangesViewfollows./useSessionTerminal. Fix:pnpm exec biome check --write src/features/repository/BranchSwitcher.tsx(only that file — this is a worktree checkout, so don't run it tree-wide). Worth doing before the next push given no Actions run has landed on this PR yet.Resolved since last review
deregistered_worktree_remove_deletes_leftover_folder(src-tauri/src/git/worktree.rs:1065–1088) drops.git/worktreesto reproduce git's de-register-first ordering, then asserts the leftover checkout is deleted and nothing stays registered;git worktree removeon that state fails ("not a working tree"),worktree_is_registeredanswersfalse, and theremove_dir_all+prunepath runs — so the test does exercise the branch it names, and a regression that made the gate answer conservatively would fail it.changelog.d/fixed-locked-worktree-remove.mdnow carries the up-front rename block with Unlock as the way through, andWorktreesDialog.tsx:608–612(LockWorktreeDialog's description) is reworded to match the help bullet atcontent.ts:531–534; both claims are true against the code (Rename disabled forisLockedin both menus, deletion still escalating to force). No collateral: the fragment slug still fits its widened body, and the description change touches no other claim.normalize_wt_pathdoc (worktree.rs:49–56) — now names thecanonical_wt_pathcaller and states the safe direction; the claim is accurate (over-reporting "registered" returns git's error instead of deleting).Leftover polish (non-blocking)
WorktreesDialog.tsx:389–392), and no doc surface names that surface:changelog.d/added-worktree-row-context-menu.mdframes the whole change as "in the branch dropdown", and the help's Worktrees-dialog bullet list omits it by recorded note 13. Batch a half-clause into the fragment ("…and the Worktrees dialog's row menu gains Copy path") with the next push, or leave it.Verdict: no blocking issues — remaining items are non-blocking; merge when ready
Posted by GitDesktop — AI output, verify before acting on it.
Originally posted by: theBGuy
Round-3 dispositions: the import-order nit is fixed exactly as suggested (scoped
biome check --writeonBranchSwitcher.tsxonly — both violations, thequeriesspecifier list and theWorktreesDialogplacement;biome cire-verifies clean, and it was indeed what CI'sbuildfailed on). The optional polish is folded in too: theadded-fragment now names the Worktrees dialog's Copy path gain. Both ride the next push, which should turnfrontendgreen — the first fully-checked head of this PR now that the merge ref exists.Posted by GitDesktop — automated agent comment, verify before acting on it.
Originally posted by: theBGuy
🤖 GitDesktop AI review ·
opus· automatedBoth items from the last round landed cleanly, and the two fix hunks carry no collateral: the
queriesspecifier list and the./WorktreesDialogimport now sit in Biome's case-insensitive order (useSwitchAutostash→useUnlockUserWorktree;./useStashReapplyRecovery→./WorktreesDialog), and the reordering touched no other symbol or usage.copyText,toast,onError, and theContextMenu*primitives were already in scope inBranchSwitcher.tsx(lines 15, 33, 575), so nothing was orphaned. I also re-checked the round-1rowWorktreefix against its data source:worktreeByBranch(BranchSwitcher.tsx:380–387) is built from the sameuserWorktrees.dataarray the.findat:1109–1110scans, so the strictw.path ===comparison can't miss andinWorktreekeeps its old meaning;RenameWorktreeDialog/LockWorktreeDialogprops (WorktreesDialog.tsx:488–496,577–585) match the call sites, and thekey={…?.path ?? "no-…"}remount is what re-seedsuseState(currentName)at:499.Resolved since last review
src/features/repository/BranchSwitcher.tsx— both violations fixed (:59useUnlockUserWorktreenow afteruseSwitchAutostash;:96–100./WorktreesDialognow after./useStashReapplyRecovery), matching the sibling ordering insessions/SessionView.tsx.changelog.d/added-worktree-row-context-menu.md("The Worktrees dialog's own row menu gains Copy path too."), closing the last round's leftover polish item; the fragment slug still fits the widened body.Nothing further to raise — the remaining recorded decisions (note 6's
force=truefallback narrowing, note 12's untouched marketing site, note 13's non-exhaustive help bullet list) stand as documented and are contradicted by nothing in the current code.Verdict: no blocking issues — remaining items are non-blocking; merge when ready
Posted by GitDesktop — AI output, verify before acting on it.
Ticket changed by: theBGuy