cmux NEXT: a new chat's text left when its tab opens another chat comes back in the window's next New Tab page
Fix-forward for #32276, which was found in the review of #32131. Fixes #32276.
What was lost
Picking another chat from a new chat's tab cleared what the new chat held. That covers the sidebar, New Tab's chat list (chat.select) and the spare page's adopt (chat.adopt). The host keeps a new chat's text with its tab only until the tab has a session (AgentUnsentDrafts.wire clears it on every onSessionChange), and a pick and the new chat's own start reach the host as the same chat.persistSession. So two things were lost:
- typed text (on base);
- since #32131, prompts held for a folder trust answer.
Now
- Page: before the pick's
persistSession, the page sendschat.leftover {text}with the new chat's kept, held and typed text. The held prompts leave the hold, so none of them is sent later into any chat. - Host:
AgentUnsentDrafts.leave(_:inWindow:)keeps the text in the window's single slot. The slot sits in the same on-disk file as the tabs' drafts (the relaunch seed's store), so it survives closing the tab and quitting the app. A second leftover is added after the first and never overwrites it. The window comes from the tab's workspace (WindowManager.windowID(ofWorkspace:)). - New Tab: every web New Tab page made in the window gets the slot's text as
newTab.draft, which its field shows as an editable draft. That covers Cmd-T, a split, a workspace's first page, a restored page, and a chat that becomes a New Tab page (AgentPaneModel.newTabForPage). Nothing sends it. The slot gives up only what a page of its own window reports it showed (chat.leftoverTaken), so a page whose tab never opens loses nothing. - A pick from that page: while the field holds a leftover (as edited), a pick from the page leaves that text again. A query typed to find a chat isn't left.
The optional native New Tab page (tabs.nativeNewTabPage, off by default) doesn't take the slot, so the text waits for the next web page. This change adds no navigation change and flips no default.
Tests
These were committed before each fix:
UnsentDraftTests.aNewChatsTextLeftWhenItsTabOpensAnotherChatWaitsForTheWindowsNextNewChat:- a pick clears the tab's own copy but not the slot, and a chat with a session leaves nothing;
- a second leftover adds to the first, and the slot survives a relaunch;
- reading the slot gives nothing up; a page's
chat.leftoverTakengives up what it showed and keeps what came after; - a page given none reads its window's, and another window's identical slot is untouched.
agent-pane-unsent-draft.test.ts:- a new chat's typed text reaches
chat.leftoveronchat.select; - a New Tab page shows
newTab.draftand acknowledges it once, sending nothing; - on both templates, an unedited or edited leftover is left again on a pick;
- a typed query is not left.
- a new chat's typed text reaches
new-tab-leftover-draft.test.ts: the New Tab page readsnewTab.draft, and a blank one counts as none.trust-held-prompts.test.tsx:- a pick pauses the new chat's held prompts until it lands, then takes them, and a failed pick resumes them;
- a prompt whose start the pick cancelled is taken with the rest, whether it settles before the pick lands or after.
Command: cmux-ci test --setup playwright -- bash -c 'cd webviews && bun test test/agent-pane-unsent-draft.test.ts test/new-tab-leftover-draft.test.ts test/agent-pane-trust-holds-prompts.test.ts test/trust-held-prompts.test.tsx'
Observed / Not run
- Observed:
- Swift test reds that aren't this PR's: ActionCatalogTests and ActionSurfaceParityTests (
newTab.default,reopenClosedBrowserPanel, export freshness). They come from base's #32127 and its autofix; this PR changes no actions. - Not run: the red half of the browser tests on the fleet.
cmux-ci testruns only the PR head, so the red commits were not run there.
Follow-up filed: #32443, for two New Tab pages that open before either reports showing the leftover.
Risk
Normal: drafts and persistence. The defaults guard flags NewTabPage.open and .split only because their source changed. No setting or default changes. The slot is a reserved key (left-in-new-chat:<window>) in agent-unsent-drafts.json, which no tab id can collide with. Nothing in this change sends a prompt. Chips aren't carried: text only, as with the tab's existing unsent draft.
š¤ Generated with Claude Code
Agent session: agent session
![View with [code]smith](https://pr-comments-assets.blacksmith.sh/codesmith/view-with-codesmith-dark-v3-3x.png)
![Autofix with [code]smith](https://pr-comments-assets.blacksmith.sh/codesmith/autofix-with-codesmith-dark-3x.png)
Need help on this PR? Tag @codesmith-bot with what you need.
Summary by cubic
Fixes the loss of a new chat's typed text and prompts held for a trust answer when its tab opens another chat; the text now comes back in the window's next New Tab page as an editable draft.
Bug Fixes
- The page sends
chat.leftoverbefore the switch, so nothing is sent into any later chat and held prompts leave the hold. - The host keeps the text in a per-window on-disk slot beside the tabs' drafts; a second leftover appends, and the slot survives closing the tab and quitting the app.
- The slot gives up only what a page reports it showed (
chat.leftoverTaken) and only to its own window's pages; a page that never opens leaves it for the next one. Every New Tab page made (Cmd-T, split, a workspace's first or restored) receives it asnewTab.draft, shown as an editable draft; a pick from a page holding one leaves it again, edits included, while a query typed to find a chat is not left.