feat(app): pin worktrees to the top of the sidebar

Merged
JO
2d ago
+256-15

The sidebar gets long once a few projects have many worktrees. You can now pin the ones you keep coming back to, and they sit at the top.

What changes

  • Pin to top in a worktree's right-click or ⋯ menu moves it into a Pinned list above the projects.
  • Pinned worktrees are listed in the order you pinned them. Each row shows its project name, plus its box chip when you have more than one box.
  • It leaves its project's list while pinned, so it isn't shown twice. Worktrees handed off from it stay nested under the project.
  • Unpin puts it back. Archiving or removing a worktree drops its pin.
  • Pins are kept on this computer (berth.pinnedWorktrees in localStorage), by box and path. Another window's changes are picked up through the storage event.

Screenshots

BeforeMenuPinned
before, lightmenu, lightpinned, light
before, darkmenu, darkpinned, dark

Code

  • app/src/lib/pinned-worktrees.ts: the pin store, plus pinnedFirst, which puts rows in pin order.
  • app/src/components/sidebar/projects.tsx: PinnedWorktrees above both groupings (project and box). useRepoTree and useProjectTree leave pinned rows out of their project, with a new pinnedApart argument. The two-column and rail layouts (columns.tsx, rail-layout.tsx) pass false, because they have no Pinned list. WorktreeRow gets an optional project label.
  • app/src/components/sidebar/actions.tsx: the Pin to top / Unpin items.
  • app/src/lib/remove-worktree.ts: the pin goes once the box says the worktree went (removeWorktreeOnBox and worktreeGone), so a failed archive keeps it.

Checks

  • After merging main: pnpm typecheck:plugins, check:titles, check:csp, pnpm test (604 pass, including the new pinned-worktrees.test.ts), pnpm build, npx tsc -p e2e and the docs build all pass.
  • Browser tests: every sidebar-*.spec.ts, plus the layout specs that use the sidebar (93 tests). All pass except 3 that fail the same way on a clean main: sidebar-fleet.spec.ts "300 worktrees…" and "projects fold and open…" (expected 310 rows, got 322), and sidebar-quiet.spec.ts. sidebar-pinned.spec.ts covers pinning, its order, a reload, unpinning, and the pin going when its worktree is removed.
  • I didn't run the Go or cargo checks because no Go or Rust code changed.

Not in this PR

  • Dragging to reorder pins.
  • Pinning a project's main checkout.

🤖 Generated with Claude Code