fix(cmux-next): New Tab Chats stop reordering old chats on workspace switch
Changelog
- Fixed: the New Tab and Home "Chats" lists no longer reorder old chats or relabel them "now" when you switch workspaces; chats sort and show their age by their last real message.
Root cause
acpmux moves a session's updatedAt on every record it logs: a pane warming or resuming the chat's agent, the idle pool stopping it five minutes later, status changes, a saved draft. The New Tab Chats list (and Home lists, sidebar history, the warm snapshot) sorted and labelled by updatedAt, so each workspace switch bumped a different pair of old idle chats to "now" and they traded places. #21828's tie-break could not help: the times really changed. Leo's real session data confirms it: every chat's updatedAt was minutes old while its lastTurn.endedAt was hours old.
Fix
Each session entry now carries activityAt (summaryActivityAt, webviews/src/agent-session/acpmux/sessionList.ts), from fields the daemon summary already sends:
- a running turn:
updatedAt(its events are real activity) - waiting on the person:
inputSince - otherwise the last turn's
endedAt, elsecreatedAt, so a chat with no turn ended here (adopted) holds a stable place and never jumps to now
The New Tab Chats cards and their child rows, Home lists, sidebar history, the warm snapshot cut, and warmRecentProjects (which re-warmed whatever it had just warmed) all sort and label by it. Entries cut by older producers (gallery fixtures) fall back to updatedAt. The New Tab attention widget's dismiss key (a dismissed chat came back on every warm), its recent projects and the chief dock use it too. A turn that never ended on a chat no longer running dates from its start.
Daemon: the summary already exposes the activity times, so updatedAt keeps its meaning for the hub's own uses. One gap fixed at the source: Quit Everything (hub/shutdown.rs settle_cancelled) now records the cancelled turn as lastTurn like every other turn end, so a chat quit mid-turn dates from that turn. Chats with turns from before lastTurn existed date from creation (stable) until their next turn.
Relation to #22115 (one chat store for every page): no overlapping lines; this works whether or not it merges.
Tests
webviews/test/new-tab-activity-time.test.ts(committed first, failing on the base): a workspace switch that warms or stops old chats leaves the Chats order and ages alone; a running turn reads now; a later real turn moves a chat up. Passes with the fix.- New Tab, Home lists, sidebar and lifecycle suites: 150 pass, 0 fail.
bun run typecheckandvp checkpass. acpmux:cargo check -p acpmuxandcargo test -p acpmux --test durable_agents(22 pass, includes the quit path) on big-red. Thecheck:text-focusandcheck:dialog-keysreds are Swift files on the base, untouched here.
Observed / Not run
- Observed:
bun test test/new-tab-activity-time.test.tsfails on the base (9470dec5ba0: old chats reorder and read their warm time) and passes on the head (4 pass). Leo's real nightly session data shows every chat'supdatedAtminutes old whilelastTurn.endedAtis hours old. - Ran: New Tab, Home lists, sidebar and lifecycle suites (150 pass),
vp check,cargo check -p acpmux,cargo test -p acpmux --test durable_agents(22 pass). - Not run: a tagged app build clicking between workspaces; CI and Leo's next nightly cover that.
🤖 Generated with Claude Code