Menu

#152 feat(worktrees,branches): add worktree row context menu in branch switcher

closed
nobody
2026-08-07
2026-08-07
Anonymous
No

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.

Branch switcher

  • Wraps each row in the Worktrees section of 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.
  • Menu offers Open worktree, Copy path, Rename…, Lock…/Unlock, Promote to main workspace…, and Delete worktree…, with disabled reasons surfaced in the item label: Rename… (main workspace) and Delete worktree… (main workspace) for the main worktree, Promote to main workspace… (locked) for a locked one.
  • Gates items on the worktree's own capabilities: rename/delete are disabled for 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.
  • Unlock mutates in place via the new 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.
  • Adds 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.
  • Renames the branch-row item from Remove worktree… to Delete worktree… so both entry points read the same.

Worktrees dialog

  • Exports RenameWorktreeDialog and LockWorktreeDialog from src/features/repository/WorktreesDialog.tsx so the branch switcher reuses those flows rather than duplicating them.

Git backend

  • 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.

Documentation

  • 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.
  • Adds changelog.d/added-worktree-row-context-menu.md.

Discussion

  • Anonymous

    Anonymous - 2026-08-07
     
  • Anonymous

    Anonymous - 2026-08-07

    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 pending reset, Copy path mirrored into the dialog) beyond the two commits the description covers.

    1. 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 in git_worktree_remove, a testable remove_worktree inner fn, and three real-repo regression tests whose negative controls were each observed failing against the pre-fix code.

    2. 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.

    3. No new command-palette actions — these are per-row targets the palette can't address. worktrees, promote-worktree-to-main, and open-main-workspace already exist; the branch-row menu items follow the same no-palette-twin pattern.

    4. 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.

    5. --force is 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).

    6. The fallback guard is registration-based and now applies to force=true too — 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 -f fails after the entry is gone). What this gives up vs. master: master's force=true fallback deleted the folder on any git failure, including a corrupted .git pointer 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 own remove -f -f succeeds there, probed), and the narrowing forecloses master's latent worst case — a refused main-worktree remove falling through to remove_dir_all on the main checkout.

    7. Three other single---force removes are exempt by designbranches.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.

    8. DeleteWorktreeDialog gains key={path} — its forceNeeded was 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: a force=false error mutates dialog-local state only; the key (target path) doesn't change mid-escalation.

    9. PromoteWorktreeDialog now resets pending on success — the success path previously relied on the keyed remount to clear it; one setPending(false) makes the key cosmetic instead of load-bearing.

    10. No isCurrent gate on the row menusotherWorktrees already 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 when currentWorktree resolves to nothing (the comparison demonstrably failed), not a same-predicate re-check.

    11. 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:

    1. Marketing site untouched deliberatelycapabilities.ts already lists the worktree manager and index.astro has no worktree FeatureRow; README, in-app help, and three changelog fragments carry the docs.

    2. 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.

    3. Support note: a ghost entry produced by a pre-fix build can't self-heal (git worktree prune skips 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.

     
  • Anonymous

    Anonymous - 2026-08-07

    Originally posted by: theBGuy

    🤖 GitDesktop AI security audit · opus · automated


    No security issues found in these changes.

    The one security-relevant behavior change — replacing the worktree_has_uncommitted_changes gate with worktree_is_registered before the std::fs::remove_dir_all fallback in remove_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 previous force=true "delete on any git failure" branch is gone). Git is spawned via Command::new(git) with an argument array (no shell), the path reaching worktree remove --force --force <path> originates from git's own worktree list --porcelain output or app-generated session dirs rather than free-form user/remote input, and every force=true caller is either a user-confirmed dialog escalation or an app-created session worktree. On the frontend, the new context menu renders w.branch/baseName(w.path) through ordinary JSX escaping and sets the tooltip via the title DOM property (no dangerouslySetInnerHTML, 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.

     
  • Anonymous

    Anonymous - 2026-08-07

    Originally posted by: theBGuy

    🤖 GitDesktop AI review · opus · automated


    Summary

    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 --force for locked worktrees plus a registration-based gate on the remove_dir_all fallback, with three real-repo regression tests. The backend change is sound and closes a genuine data-loss hole (on master, a refused force=false removal of a clean main worktree fell straight through to std::fs::remove_dir_all on the user's main checkout). Nothing blocking; two should-fixes below.

    Correctness

    • should-fixBranchSwitcher.tsx:1374–1388, the branch-row Delete worktree… item this diff relabels: it can target the main worktree. inWorktree is worktreeByBranch.has(branch.name) (line 1047) and worktreeByBranch (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… with wt.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 the onClick in renderBranchRowconst rowWorktree = (userWorktrees.data ?? []).find((w) => w.path === worktreeByBranch.get(branch.name)); — then render the item as disabled={!rowWorktree || rowWorktree.isMain} with the label rowWorktree?.isMain ? "Delete worktree… (main workspace)" : "Delete worktree…" and drop the now-redundant in-handler find/if (!wt) return. No doc knock-on: content.ts:545–547 describes that item without claiming main is deletable.

    Tests

    • should-fixsrc-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 to remove_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=false on a clean main worktree passes worktree_has_uncommitted_changes → false and 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"); }

    (_base keeps the TempDir alive; registry/setup_repo are the helpers added at lines 927–951. A second assert with force=true covers the narrowed force path for one extra line.)

    Nits

    • BranchSwitcher.tsx:1684–1693 and WorktreesDialog.tsx:393–402Rename… stays enabled for a locked worktree, but git worktree move refuses one (per git_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:530 would then read "won't prune, move, or remove it without a forced confirmation".
    • DeleteWorktreeDialog.tsx:48/force|modified|untracked|locked/i also matches git's interpolated path, so a worktree folder named e.g. locked-experiments turns 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 — the git_worktree_remove docstring still says force "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 second isCurrent predicate (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 reaches sessions/store.ts:817 (discard passes force=true), where a corrupt-.git session 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.

     
  • Anonymous

    Anonymous - 2026-08-07

    Originally posted by: theBGuy

    Dispositions for the AI review above — all five findings verified against probes and accepted; everything lands in the next push.

    • Branch-row "Delete worktree…" on the main worktree (should-fix): fixed as suggested. rowWorktree is hoisted in renderBranchRow, 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 tree in both force modes, which the escalation vocabulary correctly doesn't match.
    • Main-worktree regression test (should-fix): added, with your sketch's shape plus the suggested force=true arm folded into one loop — both modes expect_err naming "main working tree", checkout and registration asserted intact. cargo test: 991 green.
    • Rename on a locked worktree (nit): gated in both menus with the (locked) suffix (probe: git worktree move refuses "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.
    • Escalation regex matches the interpolated path (nit): fixed as a class, not an instance. The path is stripped before matching in DeleteWorktreeDialog, and the same strip went into PromoteWorktreeDialog'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 %s carries 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-marker includes over 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_remove docstring (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.ts discard matches our read — the corrupt-.git case now surfaces and stays retryable, consistent with recorded note 6.


    Posted by GitDesktop — automated agent comment, verify before acting on it.

     
  • Anonymous

    Anonymous - 2026-08-07

    Originally posted by: theBGuy

    🤖 GitDesktop AI review · opus · automated


    Re-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 the remove_worktree rewrite (doubled --force plus a registration-based gate on the remove_dir_all fallback) 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 in sessions/store.ts) stays a recorded decision, not re-litigated.

    Tests

    • should-fixsrc-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 returns false, remove_dir_all + prune finish 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 made worktree_is_registered answer conservatively (a parse_worktree_porcelain change, run_git_raw returning non-zero and hitting the _ => true arm) 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/AppState are 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>

      let state = AppState::default();
      remove_worktree(&state, &repo_s, &wt_s, None, false)
          .await
          .expect("a de-registered leftover folder is finished off, not reported");
      assert!(!wt.exists(), "the leftover checkout is deleted");
      assert!(
          !registry(&repo_s).await.contains("/orphan-wt"),
          "nothing is left in the registry"
      );
    

    }
    ```

    Documentation

    • should-fix — the locked-rename block is a user-visible change to an existing surface (WorktreesDialog.tsx:393–402Rename… was enabled for a locked worktree and ended in a raw cannot move a locked working tree toast; it is now disabled with a (locked) label; same gate added at BranchSwitcher.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.md lists rename only as a new-menu capability, fixed-locked-worktree-remove.md covers deletion only. Add a line to fixed-locked-worktree-remove.md (or a new changed-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's DialogDescription — 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 at content.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:80 already says "prune/move/remove", and the site is untouched by recorded decision 12.

    Nits

    • src-tauri/src/git/worktree.rs:50–56normalize_wt_path's doc claims "the only paths compared are app-generated session dirs vs. git's own output"; the new canonical_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

    • Branch-row Delete worktree… on the main worktree — rowWorktree is hoisted at BranchSwitcher.tsx:1050–1053, the item is disabled={rowWorktree?.isMain} with the (main workspace) suffix, and the in-handler find/if (!wt) return guard is gone; inWorktree semantics are preserved (map values come from the same userWorktrees.data array, so find succeeds exactly when has did).
    • Missing main-worktree regression test — 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 offers move -f -f.
    • Escalation regex matching the interpolated path — path stripped before the test in DeleteWorktreeDialog.tsx:50 and in PromoteWorktreeDialog.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_remove docstring — 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.

     
  • Anonymous

    Anonymous - 2026-08-07

    Originally posted by: theBGuy

    Round-2 dispositions — all three accepted and staged for the next push:

    • Fallback delete-branch test: added as sketched (admin dir dropped to reproduce git's de-register-first ordering; the leftover folder is finished off and nothing remains registered). Verified green — the Rust suite is at 992.
    • Locked-rename doc gap: both surfaces fixed. The fixed-locked-worktree-remove fragment now covers the up-front rename block with Unlock as the way through, and LockWorktreeDialog'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_path doc: 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 CONFLICTING with master (the [#151] merge landed just before it opened), and GitHub creates no pull_request workflow 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 while fragment/rust-tests never appeared. The conflict is confined to BranchSwitcher.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: #151

  • Anonymous

    Anonymous - 2026-08-07

    Originally posted by: theBGuy

    🤖 GitDesktop AI review · opus · automated


    Re-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-gated remove_worktree rewrite still hold up, and all three items from the last round landed correctly — the new fallback-delete test, the locked-rename doc lines, and the normalize_wt_path reword each check out on their own hunks. Nothing blocking; one mechanical import-order nit is all that remains. The force=true fallback narrowing (note 6) and the untouched marketing site (note 12) stay recorded decisions, not re-litigated.

    Nits

    • nitsrc/features/repository/BranchSwitcher.tsx:59 and :96: two import-order violations that biome ci will fail on (biome.json sets assist.actions.source.organizeImports: "on", Biome 2.5.6, and the assist is checked in CI even though biome lint alone skips it). (1) useUnlockUserWorktree is inserted before useSwitchAutostash in the @/lib/git/queries specifier list — case-insensitive order puts useSwitchAutostash first. (2) import { LockWorktreeDialog, RenameWorktreeDialog } from "./WorktreesDialog"; sits between ./useOpenRepoByPath and ./useStashReapplyRecovery; the repo's (Biome-enforced) order is case-insensitive, so ./WorktreesDialog belongs after both use* imports — cf. src/features/sessions/SessionView.tsx:41–43 where ./WorktreeChangesView follows ./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

    • Fallback-delete coverage — deregistered_worktree_remove_deletes_leftover_folder (src-tauri/src/git/worktree.rs:1065–1088) drops .git/worktrees to reproduce git's de-register-first ordering, then asserts the leftover checkout is deleted and nothing stays registered; git worktree remove on that state fails ("not a working tree"), worktree_is_registered answers false, and the remove_dir_all + prune path runs — so the test does exercise the branch it names, and a regression that made the gate answer conservatively would fail it.
    • Locked-rename documentation — changelog.d/fixed-locked-worktree-remove.md now carries the up-front rename block with Unlock as the way through, and WorktreesDialog.tsx:608–612 (LockWorktreeDialog's description) is reworded to match the help bullet at content.ts:531–534; both claims are true against the code (Rename disabled for isLocked in 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_path doc (worktree.rs:49–56) — now names the canonical_wt_path caller and states the safe direction; the claim is accurate (over-reporting "registered" returns git's error instead of deleting).

    Leftover polish (non-blocking)

    • The Worktrees dialog row menu also gained Copy path (WorktreesDialog.tsx:389–392), and no doc surface names that surface: changelog.d/added-worktree-row-context-menu.md frames 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.

     
  • Anonymous

    Anonymous - 2026-08-07

    Originally posted by: theBGuy

    Round-3 dispositions: the import-order nit is fixed exactly as suggested (scoped biome check --write on BranchSwitcher.tsx only — both violations, the queries specifier list and the WorktreesDialog placement; biome ci re-verifies clean, and it was indeed what CI's build failed on). The optional polish is folded in too: the added- fragment now names the Worktrees dialog's Copy path gain. Both ride the next push, which should turn frontend green — 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.

     
  • Anonymous

    Anonymous - 2026-08-07

    Originally posted by: theBGuy

    🤖 GitDesktop AI review · opus · automated


    Both items from the last round landed cleanly, and the two fix hunks carry no collateral: the queries specifier list and the ./WorktreesDialog import now sit in Biome's case-insensitive order (useSwitchAutostashuseUnlockUserWorktree; ./useStashReapplyRecovery./WorktreesDialog), and the reordering touched no other symbol or usage. copyText, toast, onError, and the ContextMenu* primitives were already in scope in BranchSwitcher.tsx (lines 15, 33, 575), so nothing was orphaned. I also re-checked the round-1 rowWorktree fix against its data source: worktreeByBranch (BranchSwitcher.tsx:380–387) is built from the same userWorktrees.data array the .find at :1109–1110 scans, so the strict w.path === comparison can't miss and inWorktree keeps its old meaning; RenameWorktreeDialog/LockWorktreeDialog props (WorktreesDialog.tsx:488–496, 577–585) match the call sites, and the key={…?.path ?? "no-…"} remount is what re-seeds useState(currentName) at :499.

    Resolved since last review

    • Import order in src/features/repository/BranchSwitcher.tsx — both violations fixed (:59 useUnlockUserWorktree now after useSwitchAutostash; :96–100 ./WorktreesDialog now after ./useStashReapplyRecovery), matching the sibling ordering in sessions/SessionView.tsx.
    • The Worktrees-dialog Copy path gain is now named in 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=true fallback 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.

     
  • Anonymous

    Anonymous - 2026-08-07

    Ticket changed by: theBGuy

    • status: open --> closed
     

Log in to post a comment.