diff --git a/ui/src/pages/chat/chat-pane-context.ts b/ui/src/pages/chat/chat-pane-context.ts index 0fe01565612a..feb2d1d45ad9 100644 --- a/ui/src/pages/chat/chat-pane-context.ts +++ b/ui/src/pages/chat/chat-pane-context.ts @@ -105,7 +105,11 @@ export abstract class ChatPaneContext extends ChatPaneLifecycle { this.refreshSwarmRoster(); const selectedSession = selectedChatSessionRow(state); if (applySelectedSessionProjection(state, selectedSession)) { - this.markSessionRead(selectedSession); + // Hidden retained panes keep this subscription alive; only the pane the + // user is actually looking at may clear unread/attention state. + if (this.presented) { + this.markSessionRead(selectedSession); + } } this.syncSessionSuggestionTarget( stateValue.agentId ?? resolveChatAgentId(state) ?? "main", diff --git a/ui/src/pages/chat/chat-pane.read-marker.test.ts b/ui/src/pages/chat/chat-pane.read-marker.test.ts index 08dd795f4840..264ae741136b 100644 --- a/ui/src/pages/chat/chat-pane.read-marker.test.ts +++ b/ui/src/pages/chat/chat-pane.read-marker.test.ts @@ -89,4 +89,44 @@ describe("chat pane read markers", () => { expect(state.chatError).toBeNull(); expect(state.lastError).toBeNull(); }); + + it("does not clear unread from a hidden retained pane", () => { + const patch = vi.fn().mockResolvedValue(null); + const { pane } = createTestChatPane({ + client: {} as GatewayBrowserClient, + sessions: { patch } as unknown as SessionCapability, + }); + const sessionsState = (presented: boolean) => { + pane.presented = presented; + pane.applySessionsState({ + result: { + sessions: [ + { + key: "agent:main:current", + kind: "direct", + label: "Background activity", + updatedAt: 20, + unread: true, + }, + ], + }, + agentId: "main", + loading: false, + error: null, + deletedSessions: [], + } as unknown as Parameters[0]); + }; + + // Hidden retained panes keep the subscription alive but must not mark + // the session read — the user is not looking at it. + sessionsState(false); + expect(patch).not.toHaveBeenCalled(); + + sessionsState(true); + expect(patch).toHaveBeenCalledWith( + "agent:main:current", + { unread: false }, + { agentId: "main" }, + ); + }); }); diff --git a/ui/src/pages/chat/chat-pane.test-support.ts b/ui/src/pages/chat/chat-pane.test-support.ts index b24be06c76ee..c2d51a398b84 100644 --- a/ui/src/pages/chat/chat-pane.test-support.ts +++ b/ui/src/pages/chat/chat-pane.test-support.ts @@ -134,6 +134,7 @@ export type TestChatPane = HTMLElement & { headerPlacementReclaimingKey: string | null; reclaimHeaderPlacement: (row: GatewaySessionRow) => Promise; markSessionRead: (row: GatewaySessionRow | undefined) => void; + applySessionsState: (stateValue: ApplicationContext["sessions"]["state"]) => void; renderPaneHeader: ( workspace: ReturnType, tasks: ReturnType,