cmux-next project picker: one row per repository, no lane scratch folders, newest first
Leo-ask: ask-e773c7223b ("do i add cmux in /projects or": either folder now gives one cmux row)
What
Leo's New Tab project picker on nightly 331e2e9 listed cmux twice ("cmux · ~/Projects" with 19 branches and again with 63), each lane checkout and .noindex scratch folder as a project of its own (cmux-live-block.noindex, lane-host-fg, cmux-steer-folds.noindex, cmux · queued-agent-from), and the home folder. Why: the two cmux rows were two different Git folders of one GitHub repository (the main clone ~/Projects/cmux and the lane mirror ~/Projects/.wt-mirrors/manaflow-ai/cmux.git that every checkouts/cmux/* worktree shares), and the pickers grouped by Git folder. The lane folders came from chats: project.list reads repositories only for its own folders, so a chat's folder arrived with none and drew as its own project.
groupByRepo (projectRepos.ts) now:
- Groups by the GitHub
owner/namewhen known, else the main checkout: every clone and worktree of one repository is one row, with the branch count as its disclosure. - Places a folder the host said nothing about: inside a checkout it knows (
~/Projects/cmux/webviews), else by its lane path (checkouts/<repo>/<branch>with--for/,<repo>-worktrees/<lane>, and the existing~/.cmux/worktrees(.noindex),.claude/worktrees, Conductor, Codex homes) when exactly one known repository has that name. - Leaves out
.noindex,.cache,/tmpand/var/foldersscratch folders that belong to no repository, and the home folder itself, unless it is the current folder.
The New Tab and composer pickers order projects by their newest chat (byRecentUse), then the host's order. Action rows (Do not work in a project, Add project, Choose folder…) and worktree branch glyphs line up with the names.
Not in this PR:
- Two-letter tiles and the grey dot on every local project: austinywang's #23756 replaces them with framework marks and a folder fallback, so this PR leaves
ProjectBadgealone. - The "blue floating button with an up-arrow-house glyph" over the list's top-left corner: nothing in the page draws there (checked with
elementsFromPointin Chromium over the open popover; the popover's positioner is topmost). It matches macOS's Caps Lock indicator, a blue capsule with ⇪ that macOS draws under the text cursor of a focused field when Caps Lock is on. The search field takes focus when the popover opens, so the indicator lands on the list's top-left corner.
Screenshots
Gallery before/after (Linux Chromium, light and dark): run 39698993302, artifact gallery-advisory-23892-c80517ec8d97e06d022084697b683f0e34ada74b. New states agent-pane.composer-context/lane-checkouts (Leo's list: before, two cmux rows plus every lane and .noindex folder; after, cmux once with its branch count, leo-workspace, atlas-web, then New Worktree and Add project) and lane-checkouts-open (cmux opened to its 3 worktrees). duplicate-search moves 1.5% from the aligned action rows; every other touched state is unchanged.
Real app: the media-tour-mac tour of this head posts to the status comment.
Review
Independent subagent review (correctness first), SHIP at c80517ec8d97e06d022084697b683f0e34ada74b. The first pass found two bugs, fixed in c80517e: a folder the host said nothing about could lead a known repository by its name alone (now it joins only by its checkout or an unambiguous lane path), and the flat pickers (New Tab hero, environment card) still listed lane and scratch folders (now projectRows, one per repository). Left as follow-ups, none a regression: three flat lists that bypass projectRows (the Choose… chooser, NewTabPage's chooser, SavedEnvironmentPanel) now order by recency without dropping lanes; current with a trailing slash is compared exactly; a repository with only lanes is named after its newest lane.
Tests
webviews/src/agent-session/acpmux/projectRepos.test.ts, run with bun test src/agent-session/acpmux/projectRepos.test.ts:
one row per repository: every clone and lane checkout of one GitHub repository folds together(Leo's list: 3 rows, cmux with 6 folders, the main clone leading)scratch folders in no repository and the home folder are left out unless currenta lane checkout's guess joins only an unambiguous repository of that nameprojects a chat used come first, newest chat first; the rest keep their order
With the base's projectRepos.ts (cc945e2e7d1) the first three fail (8 pass, 3 fail, before the byRecentUse test existed); on the head all 14 pass, with the flat pickers list each repository once, by its lead, and no scratch folders and a folder the host said nothing about never joins a known repository by its name alone added for the review fixes.
Observed / Not run
Observed: bun run check passes (its one warning, system blue in ui/importCards.css, is on the base). ComposerContext, FolderChoice, HomeLists, composer-location-row-new-chat-only and new-tab-folder-keeps-pick pass. test/composer-location-strip.test.ts with CMUX_BROWSER_TESTS=1 on huge-blue: 23 pass; "the chat composer at 340 px: a folder name cuts in the middle" failed once on a loaded host. That test covers the strip's face, which this PR doesn't change.
Not run: no Swift change, so no compile.
Changelog
- Fixed: the project pickers list each repository once, with its clones and worktrees as branches under it, and no longer list lane scratch folders or the home folder.
Agent session: huge-blue
🤖 Generated with Claude Code