Repository navigation
One chat in two tabs: Session Info and the transcript place are kept per tab; a Session Info popover gets its glass - #34492
Conversation
… place; a popover over the chat gets its glass Red on base: the per-tab store API, the tab-aware place and the panel's overChat do not exist, and the popover model carries no overChat (bun: 2 fail, 1 error). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Agent-Session: https://cmux.com/deeplink/agent?id=aid-ed9226b71907d8aaa0b6
…t; a popover over the chat gets its glass One chat open as a tab in two workspaces: each tab's reload now shows its own Session Info (open, width, folds) and reopens where that tab's transcript rested, instead of the other tab's. A tab with no state of its own reads the chat's old entry. The host names the tab (handshake tab, acpmux-pane-tab for an adopted spare; the pane's tab for chat.scrollAnchor). The native Session Info panel as a short pane's popover lay over the transcript with no glass (only foot turned it on); the page now sends overChat and the panel takes its glass whenever it lies over the chat. Focused bun: test/session-info-per-tab-state.test.ts and test/native-session-info-page.test.tsx red on base, green here. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Agent-Session: https://cmux.com/deeplink/agent?id=aid-ed9226b71907d8aaa0b6
|
cmux status (one comment, edited in place; each line is updated by the job that owns it) Review App of this head (app.zip, kept 1 day)
Not verified: macOS jobs did not run: compile and app tests were skipped on CI passes on Written by Shipped in:
|
|
Review (subagent claude-opus-5-5 medium): SHIP at 6e56746 Visual verdict: no images. The only visual change is the Session Info popover's glass backing, a native panel; covered by code and tests. Checked: scroll-anchors Codable compatibility with old files, fallback order (own tab, then the chat's most recent, at-latest stays nil), the double write and index pruning in sessionInfoState.ts, the path the tab key takes to the page (handshake, Follow-ups:
Checks at the head: app compile check passed. Focused Bun tests are green. Fleet |
|
Review (codex, router-review): SHIP at 6e56746 Reviewer: codex gpt-6-astra high, fast tier. Follow-ups: 1. Each lands as an issue in this repo that links this receipt; the lane that opened the PR fixes it forward. SHIP at 6e56746 Visual verdict: no new visual regression in the 16 supplied before/after pairs. Native popover glass is not pictured.
Bun and Swift suites could not run because those runtimes are unavailable. Follow-ups:
|
Leo, 2026-11-03, on one chat open as a tab in two workspaces: "i feel like right split is fine on click by default (extra view doesn't really affect anything), and you can riht click to go to it. but of course we just have to be prepared to mitigate oddities"
So two views of one session have to be safe. Two pieces of per-view state were kept by session, not by tab, so a reload of one tab took the other tab's state:
cmux.agentPane.inspector.pane.<sessionId>. Session keying came from cmux NEXT agent chat: Session Info opens and closes per pane #25169 ("Session Info opens and closes per pane", 2026-10-25), carried through the rename in carry #29651: Session Info is called Session Info in its code, not Inspector #29921.AgentPaneScrollAnchors,chat.scrollAnchor/chat.scrollAnchor.get), keyed bysessionIdsince cmux-next: chats reopen where you left them after a relaunch (cx-6bgzj) #19445 (cx-6bgzj); moved to the state files in cmux NEXT: agent pane state files save off the main thread #27749 and fix(quit): state files have one owner each and no quit blocks the main thread on them #28275.What changes
AgentPaneModel.tabKey(the store's tab id, resolved at each call).AgentUnsentDrafts.wirealready gets the tab key for every tab's model, a spare's too when a tab adopts it, so it sets it there. No App-target lines added.AgentPaneBridgeandAgentPageProvider) pass the pane's tab to the store. Entries now carrytab, and an at-latest entry is kept for its tab, so that tab's reload doesn't fall back to the other tab's place. Lookup order: the tab's own entry, then the chat's most recent entry from any tab. That includes old tabless entries, so existing places still restore, and a tab opened on the chat later still opens where the chat last rested. The page never names its own tab.tab. A spare that a tab adopts after its handshake getsacpmux-pane-tab.sessionInfoState.tskeeps a tab's state undertab:<tab>:<paneKey>, and each save also writes the chat's key. So the chat key holds the latest state in any tab, which is the fallback for a tab with no state of its own (including chats saved before this change). Both keys go in the index and are pruned the same way. Split keys (split:) work as before. With no tab (an older host, the gallery, tests), nothing changes.Ghost panel (confirmed in code, fixed here)
Leo saw a faint copy of Session Info ("Background tasks", "Connectors", "Agent memory"...) mixed into the chat transcript. One path does this.
AgentPaneSessionInfoPanel.updateBacking()gave the native panel its overlay glass only when the page sentfoot. But a short pane's popover (pane underPOPOVER_BELOW_HEIGHT, stageoverlay) lies over the transcript and sends nofoot:foot={stopsAboveComposer ? dockHeight : undefined}withstopsAboveComposer = overChat && !popover. So that panel's text sat over the transcript text with no fill (the panel itself hasbackgroundColor = nil). Now the page sendsoverChatwhenever the panel lies over the chat, and the panel takes its glass onoverChat || foot. Pages that predateoverChatbehave as before.I found no path where the page says closed while the native panel stays shown: every
pane.sessionInfoupdate, reload (pageLoaded()) and unmount clears it. One gap I did not verify: a reload that never reachesready(a load failure) would leave the old panel up. Session keying (item 1) could make a reloaded tab show the other tab's open state, but each pane's native panel still follows its own page.Tests
webviews/test/session-info-per-tab-state.test.ts(new): two tabs of one chat keep their own open, width and folds across a reload, including the first frame before the bind. A tab with no entry of its own reads the chat's old session-keyed entry and then keeps its own. Both entries are in the index. A spare that a tab adopts after binding re-reads under its tab and notifies listeners.webviews/test/native-session-info-page.test.tsx: a panel over the chat postsoverChat: trueeven with nofoot.ChatScrollRestoreTests: two tabs of one chat keep their own place across a reload, and a tab at its latest message stays there instead of taking the other tab's place. An old tabless file entry is the fallback for a tab with none and is then replaced by the tab's own.AgentPaneNativeSessionInfoTests.aPanelOverTheChatGetsItsGlassEvenWithoutAFoot.Red on base (the first commit, tests only):
cd webviews && bun test test/session-info-per-tab-state.test.ts test/native-session-info-page.test.tsxgives 2 fail and 1 error. Green at the head: 31 pass across those files plussession-info-split-starts-closedandsession-info-per-pane-state. The Swift tests reference API that doesn't exist on base (tab:andliesOverChat), so they fail to build there.Observed / Not run
vp fmtandvp linton the changed web files, the cmux-next prepush gate, and the app compile check at the head.CmuxNextAgentPaneTests). I have not run two real tabs of one chat in a DEV build.Downgrade note: an older app that reads a places file containing an at-latest tab entry (no
anchor) fails to decode it and starts with no kept places. It loses scroll places only, nothing else.Changelog
Agent session: agent session
🤖 Generated with Claude Code
Open in [code]smith · Need help on this PR? Tag
@codesmith-botwith what you need.Summary by cubic
Keeping two tabs of one chat safe now: each tab stores its own Session Info (open state, width, folds) and transcript scroll position, so a reload in one tab no longer picks up the other tab's state. A Session Info popover over the transcript also gets its proper glass background, fixing the chat showing through it.
overChatfrom the page so a short-pane popover (which sends nofoot) gets its glass.Written for commit 6e56746. Summary will update on new commits.