Repository navigation
Description
Motivation
A workspace ends up with several project tabs for the same folder. Observed on 2026-09-11: three agent-code tabs created on Sept 3, 7 and 10, each holding a different generation of agents, with worktree agents spread across them. The Dispatch index, the row project filters and the bulk modals then show three "agent-code" projects for one repository, and any "every agent in this project" action has to be repeated per tab.
The cause is that the New Tab flow never dedupes: ⌘T plus the path picker creates a fresh tab every time, even for a folder that is already open. The operator control path already has the right rule (projects.open reuses an existing tab with that exact directory unless createDuplicate is explicit); the keyboard path predates that decision. Window adoption appends a closed window's tabs without root dedupe, so it can also leave duplicates behind.
Intended behavior
- Reuse on open. When the path picker's folder is already held by an open tab, the picker says so and its primary action becomes "go to tab" (activate it); "new tab anyway" stays one click away for the cases where a second tab of one repository is wanted on purpose. Resume flows are unchanged in this issue.
- Merge Project Tabs command. Choose a target tab and the tabs to fold into it (defaulting to every tab that shares a working directory with the target). Everything moves under the target without touching any process: source grid panes and detached agents become Dispatch agents of the target, buried records and Dispatch row project filters are re-pointed, tiled-tab and spotlight/reader references to the removed tabs are cleared, then the emptied tabs are removed. A confirmation lists what moves. No session is killed, reloaded or re-spawned.
Acceptance criteria
- With a tab already holding a session in the chosen folder, the picker offers "go to tab" and "new tab anyway"; the first activates without spawning, the second spawns a new tab as today. A folder not held by any tab behaves exactly as today.
- The operator
projects.openand the picker share one "tabs holding this directory" rule. - Merging N tabs into a target leaves every session owned (same owned-session set before and after), removes the sources, re-points detached, buried and row-filter references, keeps the target's grid untouched, and never calls kill/spawn/reload.
- Merging refuses a target that is among the sources or an unknown tab.
- Deterministic unit tests for the merge planner and the directory rule; the command catalog baseline updated;
tscclean on both projects.
Activity
docs(workspace): record the Merge Project Tabs pull request in the plan
merge: origin/main into feat/merge-project-tabs