cmux NEXT New Tab: the first frame names the project and machine it keeps
Leo, 2026-10-19: "new tab properly inherits projects but new tab still paints first frame as unexplored but it's fast so it's just a tweak I think. I think it's indecisive between 'defaults' and 'recently used'".
The race
The New Tab page renders before the host's handshake and before its own daemon connection lists the chats. Its project chip came from two late inputs:
- Inherited project: the chip named
startCwd, the start folder the handshake reports. Before the handshake there was none, so the first frame read "Choose folder", then the project. - Recent project:
mostRecentProject(snapshot.sessions). A freshly mounted page's snapshot (per-pagesessionStorage) has no chats until its connection answers_acpmux/watch, so the first frame read "Choose folder", then the recent project.
The machine chip had the same issue on a cold launch: "This Mac" until the handshake's machineName arrived.
The fix
The value is decided once, before the first paint:
- Inherit: before the handshake answers (
startPending), the chip names the project the tab inherited. The handshake starts the chat there. The home folder or anything above it is still never named. - Recently used: every page that hears this Mac's chat list keeps its newest folders for the run (
newtab/recentProject.ts). All agent pages share one website data store, so a later page reads them synchronously at mount. The New Tab page decides from its own chats, else from those folders, and latches the result. If the host's project list lands and leaves that folder out (a removed project), the page falls back to the newest listed one, as before. - Unknown (the run's first page): the folder chip keeps its place but shows no label, so "Choose folder" never paints first. It holds until the daemon lists the chats (
listedSessions), for at most 2 s. - Machine: the host puts this Mac's name in the New Tab context it injects at document commit (
AgentPaneNewTab.host, read once at launch). The page already readsnewTab.hostfirst, so the chip names the machine on the first frame, even on a cold launch. A page on another machine still gets its name from its handshake.
Related open PRs: #22115 (one chat store for every page) seeds snapshot.sessions from shared storage. Once it lands, initialRecentProject picks the project from those chats first. The listedSessions hunks in direct.ts/model.ts are byte-identical to #22115's, so whichever lands second merges cleanly. #22008 and #22115 also cache the handshake's machine name per run; newTab.host comes before that cache.
Tests
webviews/test/new-tab-first-frame-project.test.ts(browser lane, Chromium + WebKit): this is the real pane dev entry with a stand-in host (late handshake) and a stand-in acpmux daemon (late chat list). A script runs from document start and records the location strip's visible text on every mutation and every animation frame.- At base
1530442f2eall 6 cases fail with["Choose folder cosmic-gray","billing cosmic-gray"]/["Choose folder cosmic-gray","atlas cosmic-gray"]. - With the fix all 6 pass:
["billing cosmic-gray"],["atlas cosmic-gray"], and for a cold page["cosmic-gray","atlas cosmic-gray"], where the folder's place is held empty first.
- At base
- jsdom:
new-tab-recent-project.test.tsxcovers the store (local origin only, home and agent-home skipped, project list respected, an empty list keeps the last), the first-render chip, and the held place.new-tab-project-chip.test.tsxcovers the inherited project before the handshake. - Swift:
NewTabFirstFrameHostTestschecks that the context encodes and decodeshost. - Run on Huge Blue: all
test/new-tab-*jsdom tests pass (99/0), lint and fmt are clean, and the typecheck adds no new errors (the only errors there are missing generatedstrings.jsonin that checkout).
Not checked: the native app was not built here (CI builds it).
š¤ Generated with Claude Code
Need help on this PR? Tag @codesmith-bot with what you need.
Summary by cubic
The New Tab page now names its project and machine on the first frame instead of painting "Choose folder" and then flipping once the handshake and daemon chat list land.
- An inherited project shows before the handshake answers, since the handshake starts the chat there; the home folder is still never named.
- Pages that hear this Mac's chat list keep their newest folders for the run in shared storage (
newtab/recentProject.ts), so a later New Tab page has a recent project synchronously at mount and latches it. A page on another machine has no home, so it never starts in one of this Mac's kept folders. - With nothing known yet, the folder chip keeps its place with no label (for at most 2 s) rather than showing a default.
- The host injects this Mac's name in the New Tab context (
AgentPaneNewTab.host), so the machine chip names the machine on a cold launch; a page on another machine still gets its name from the handshake.
Tests
- Browser tests (
new-tab-first-frame-project.test.ts) drive the real pane dev entry with a late handshake and late daemon list; all 6 cases fail at base and pass with the fix. - jsdom tests cover the shared recent-project store, the first-render chip, the inherited project, and the held place; Swift tests cover
hostencoding.