cmux NEXT New Tab: every opening paints its final chats and project on its first frame
Leo, 2026-10-19: "there's still a split second flicker upon opening up new workspaces wrt the semi dynamic content on the recent chats and the recent project... it sometimes doesnt load fast enough", and "likewise with opening up new tabs like it cant decide what default is vs what recently used is".
Cause
A New Tab page lists no chats until its own daemon connection answers. Its Chats list painted empty, then the device chats (pushed after the handshake), then the chats above them. The recent project was decided from that same late list.
Fix
- Warm snapshot on the native side. Each local page posts its newest listed chats (up to 100) and its device chats as
newTab.warm. The host keeps the last one inAgentPaneNewTab.warmed. This is untouched boot traffic, so spares stay poolable. Another machine's page never warms this Mac's pages. - One context builder.
warmedOnThisMac(renamed fromnamingThisMac) is whatNewTabPage.page()andsparePage()already call. It now also setswarm. That covers Cmd-T, the + button, a new workspace, a split, and an adopted spare (the adopt event carriespage()'s context). - Web side.
newTabHostseeds the carried chats (newtab/warmSnapshot.ts). The page lists them until its own list arrives, and the live list then changes rows in place. The recent project is decided once, at mount, from the same chats. - Adopted spare. It now takes the adopting tab's folder as its start folder. It used to keep the spare's handshake folder, so Cmd-T on a spare said "Choose folder" for a tab in a project.
- Cold page. With nothing posted yet, the page holds the device chats back with the list (at most 2 s), so the list fills once.
Changed frames, workspace open to final content
Measured by webviews/test/new-tab-warm-first-frame.test.ts in Chromium and WebKit. It records the Chats list and the location strip on every DOM change and every frame, with the page's own list 1.2 s late.
| Opening | Before | After |
|---|---|---|
| Cmd-T | 2 | 0 |
| + button | 2 | 0 |
| New workspace | 2 | 0 |
| Split | 2 | 0 |
| Adopted spare | 1, and "Choose folder" | 0 |
| Cold launch, no snapshot | 2 | 1 (the empty state once) |
On base all 12 cases fail; with this change all 12 pass, on Huge Blue.
Tests
webviews/test/new-tab-warm-first-frame.test.ts(browser): one case per entry path, plus the cold case.webviews/test/new-tab-warm-snapshot.test.ts(unit): seeding, the fallback window, and coalesced posting that is local and listed only.Packages/macOS/CmuxNext/Tests/CmuxNextAgentPaneTests/NewTabWarmSnapshotTests.swift: decoding and the size cap, the method allowlist, that the warm request stays untouched, local versus remote, and the context encoding. CI runs it; the app was not built locally.
Overlap
#22115 (one localStorage chat store for every page) and #22008 (settled chats cache) aim at the same flicker. This one follows the coordinator's call to carry the snapshot in the native newTab context. Whichever lands second should drop the duplicate path.
Not covered: the All chats list in the threads and console templates still loads chats.page after mount.
š¤ Generated with Claude Code
Need help on this PR? Tag @codesmith-bot with what you need.
Summary by cubic
Fixes the split-second flicker when opening New Tab pages, where the Chats list painted empty before the daemon answered and the recent project was decided from that late list.
- A local page posts its newest chats and device chats (
newTab.warm); the host keeps the last snapshot and the single context builder every opening uses (Cmd-T, +, new workspace, split, adopted spare) carries it into the next page. - A New Tab page lists the carried chats until its own list arrives, then swaps rows in place; the recent project is picked once, at mount, from the same chats. A page whose daemon is down shows no carried chats, and once the page's own list arrives they never come back.
- An adopted spare starts in the adopting tab's folder instead of its own handshake folder, so it no longer shows "Choose folder" for a tab in a project.
- Cold launches with no snapshot hold the device chats back with the list (up to 2 s) so the list fills once.
- Browser tests report warm opens painting their final content on the first frame (previously 1ā2 frames); the cold case paints one intermediate empty state.
- A regenerated
schemas/defaults/cmux-next.jsononly touches unrelated defaults (markdown code font scale, home pet settings).