diff --git a/ui/src/pages/chat/chat-pane-session.ts b/ui/src/pages/chat/chat-pane-session.ts index 2ccd03e53966..c7036617c1cc 100644 --- a/ui/src/pages/chat/chat-pane-session.ts +++ b/ui/src/pages/chat/chat-pane-session.ts @@ -189,11 +189,20 @@ export abstract class ChatPaneSession extends ChatPaneTaskSuggestions { return; } const guardKey = state.sessionKey; - void this.context.sessions.patch(row.key, { unread: false }, { agentId }).catch(() => { - // Unlatch so later unread snapshots retry; the session capability - // publishes the actionable error for the owning page. - this.unreadPatchGuard.patchFailed(guardKey); - }); + void this.context.sessions.patch(row.key, { unread: false }, { agentId }).then( + (result) => { + // A null result means no request was sent (connection scope lost); + // unlatch like a failure or the badge stays lit until navigation. + if (result === null) { + this.unreadPatchGuard.patchFailed(guardKey); + } + }, + () => { + // Unlatch so later unread snapshots retry; the session capability + // publishes the actionable error for the owning page. + this.unreadPatchGuard.patchFailed(guardKey); + }, + ); } protected async restoreArchivedSession(sessionKey: string, expectedSessionId: string) { 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 264ae741136b..0d8d3da7472a 100644 --- a/ui/src/pages/chat/chat-pane.read-marker.test.ts +++ b/ui/src/pages/chat/chat-pane.read-marker.test.ts @@ -90,6 +90,30 @@ describe("chat pane read markers", () => { expect(state.lastError).toBeNull(); }); + it("retries the read patch after a null (unsent) resolution", async () => { + // sessions.patch resolves null without a request when the connection + // scope is lost; the guard must unlatch like a failure or the badge + // stays lit until navigation. + const patch = vi.fn().mockResolvedValue(null); + const { pane } = createTestChatPane({ + client: {} as GatewayBrowserClient, + sessions: { patch } as unknown as SessionCapability, + }); + const row = { + key: "agent:main:current", + kind: "direct" as const, + label: "Unread", + updatedAt: 20, + unread: true, + }; + + pane.markSessionRead(row); + await Promise.resolve(); + pane.markSessionRead(row); + + expect(patch).toHaveBeenCalledTimes(2); + }); + it("does not clear unread from a hidden retained pane", () => { const patch = vi.fn().mockResolvedValue(null); const { pane } = createTestChatPane({