Composer: show the location strip only before a chat's first send
After the first message in a chat, the composer kept showing the location strip (folder · machine · branch). Not every harness can switch folder or machine mid-chat, and cmux doesn't either, so the strip implied a choice that wasn't there. It now shows only on a new chat, before the first send. Once the chat has started, the strip is gone, and the queue and the card close up with no gap.
When the strip shows
showsLocationRow in webviews/src/agent-session/acpmux/locationRow.ts reads the session's own state, not a local flag, so it survives remounts and reloads. A chat counts as started once any of these is true:
- acpmux recorded a turn (
summary.turnCount > 0; acpmux counts it when the prompt dispatches, and older history still to load counts as one) - the transcript has a user prompt that didn't fail (this includes the in-flight optimistic row)
- a turn is running
- a prompt is waiting in the queue
Decisions:
- Reopened chat: while a selected session's summary hasn't arrived yet (or arrives without
turnCount), the strip stays hidden. A chat with history never flashes it on load. acpmux always serializesturn_count, so a real new chat getsturnCount: 0and shows the strip. - Failed first send: the refused prompt's row is
failedand acpmux recorded no turn, so the strip comes back and the location can still be picked before Retry. - Queued before the first turn: hidden. A queued prompt will run where the chat is, so the location is committed.
Other entrypoints
None of them can switch folder or machine mid-chat now:
- The started-chat strip used to offer a folder menu with New Chat Here and Choose folder…. Those opened a new chat and never moved this one, and they go away with the strip.
- The
openPicker("Location" | "Computer")automation openers register only while the strip is mounted. - The automation
pickFolderhook and the agent-home "Choose Folder…" line were already limited to a fresh chat, or start a new chat. - No keyboard shortcut or command-palette entry opens the location menus. Hotkey defaults are unchanged.
The composer no longer names the machine mid-chat. A started SSH or Cloud chat used to show the peer's name in the strip. Nothing else in the composer or header names it now. If we want that back, it should be a read-only label somewhere else.
Layout
The strip's CSS is all :has(> .acpmux-composer-strip). Without the strip, the card draws its own full rim and the queue sits right on the card. test/agent-pane-bottom-stack.test.ts now asserts the order ask → dock → queue → card and that the queue's bottom edge meets the card's top edge within 0.5 px. test/composer-location-strip.test.ts adds the same check for a started chat with and without a queue. No CSS changed, so this should rebase cleanly over #20791, #20789 and #20802. None of them had merged when this was opened. They touch the same browser tests, which measured the strip on started chats.
Stills
Gallery agent-pane.composer-strip. "Before" is origin/feat-cmux-next (c6b14309a64), "after" is this branch. The new chat is variant project; mid-chat is variant queued (a running turn with two queued prompts).
Catppuccin Latte
| width | new chat (strip shown) | mid-chat before | mid-chat after (strip gone) |
|---|---|---|---|
| 760 | ![]() | ![]() | ![]() |
| 400 | ![]() | ![]() | ![]() |
| 300 | ![]() | ![]() | ![]() |
Catppuccin Mocha
| width | new chat (strip shown) | mid-chat before | mid-chat after (strip gone) |
|---|---|---|---|
| 760 | ![]() | ![]() | ![]() |
| 400 | ![]() | ![]() | ![]() |
| 300 | ![]() | ![]() | ![]() |
Borrowed from AI Elements. The Prompt Input in shadcn's AI Elements renders its block-start addon only when there's something to act on (attachment chips appear only when files are attached), and it leaves the slot empty otherwise. The strip follows the same restraint: it shows while the location can still change, and it isn't shown as an inert read-only row after that.
Tests
Commit 1 (5bb0f7b4a24) adds webviews/test/composer-location-row-new-chat-only.test.tsx only. It renders the real Composer in jsdom, and 4 of its 5 tests failed against the base. Commit 2 (493b32f9386) adds the fix, and all 5 pass:
- a new chat with no turns shows the location strip
- after the first send the strip is gone: the prompt in flight, then the turn acpmux recorded
- a reopened chat with history never renders the strip, not even while it loads
- a failed first send shows the strip again, so the location can still be picked
- a prompt queued before the first turn hides the strip: it runs where the chat is
Commit 2 also updates the galleries and browser tests that measured the strip on started chats: composer-location-strip, agent-pane-bottom-stack, composer-row-layout, agent-pane-peer-chat, and the composer, composer-row, composer-turn-cycle and new-chat-no-shift gallery entries.
Ran in webviews/:
bun run typecheck,vp check(format and lint) and the layer, type-scale, status-glyph, system-blue and text-focus checks all pass.check:browser-lanesfails oncomposer-row-rhythm.test.tsandagent-pane-edited-calm.test.ts, both untouched here.scripts/ci/run-webviews-tests.sh: 292 files. 3 failed, and the same 3 fail on the base:diff-label-bundleanddiff-worker-bundle(need a built bundle) andstats-page(a German date format).- Browser lane, locally with
CMUX_BROWSER_TESTS=1in headless Chromium:composer-location-strip,agent-pane-bottom-stack,composer-row-layout,agent-pane-peer-chat,composer-turn-cycle,ghost-button-balance,composer-location-sideandcomposer-edgesall pass. - I didn't run the gallery play matrix locally. CI's gallery job covers the anchor changes.
Web only; no native build.
🤖 Generated with Claude Code

















