mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-27 12:56:01 -06:00
fix(ui): hidden retained chat panes silently clear unread for sessions you are not viewing (#123247)
Since retained panes (#121625) every pane — hidden or presented — keeps its sessions subscription alive, and applySessionsState marked the pane's selected session read unconditionally. New activity in a recently-viewed session was cleared by its hidden pane instantly: the sidebar unread dot never stuck, agentStatus attention was wiped by the read patch, and "Mark as unread" self-reverted. Gate the read-marking on this.presented, matching applyActiveSessionBindings.
This commit is contained in:
committed by
GitHub
parent
f391453378
commit
eecf488c32
@@ -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",
|
||||
|
||||
@@ -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<typeof pane.applySessionsState>[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" },
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -134,6 +134,7 @@ export type TestChatPane = HTMLElement & {
|
||||
headerPlacementReclaimingKey: string | null;
|
||||
reclaimHeaderPlacement: (row: GatewaySessionRow) => Promise<void>;
|
||||
markSessionRead: (row: GatewaySessionRow | undefined) => void;
|
||||
applySessionsState: (stateValue: ApplicationContext["sessions"]["state"]) => void;
|
||||
renderPaneHeader: (
|
||||
workspace: ReturnType<typeof createSessionWorkspaceProps>,
|
||||
tasks: ReturnType<typeof createBackgroundTasksProps>,
|
||||
|
||||
Reference in New Issue
Block a user