feat(session-status): mobile session quick switch with running/unread indicators (#340)

* feat(session-status): implement server-authoritative session status tracking

- Add server-side session states and attention tracking with Map storage
- Implement SSE event broadcasting (openchamber:session-status)
- Add REST API endpoints for status/attention queries
- Replace client-side polling with HTTP polling + SSE push
- Track needsAttention based on user message + completion + unviewed
- Add MobileSessionStatusBar for mobile UI
- Handle session view/unview/message-sent state transitions
- Add 24h cleanup for old session states

* feat(session-status): add unread indicator to mobile session status bar

* refactor(session-status): extract SessionStatusHeader component

Extract the session status header into a reusable component and improve
layout structure in CollapsedView and ExpandedView.

* feat(session-status): optimize mobile session status bar UI

* refactor(session-status): remove interval polling, use snapshot sync on reconnect/visibility

- unifies session status/attention sync with existing SSE lifecycle, fixes no-op store churn bug, and drops duplicated client activity state while preserving mobile unread/running UX.

---------

Co-authored-by: Jovines <jovines@qq.com>
Co-authored-by: Bohdan Triapitsyn <artmore@protonmail.com>
This commit is contained in:
Jovines
2026-02-07 03:11:55 +02:00
committed by GitHub
co-authored by Jovines Bohdan Triapitsyn
parent aa85e31420
commit d5a2e49ae8
10 changed files with 1283 additions and 146 deletions
+21
View File
@@ -99,6 +99,7 @@ export const useSessionStore = create<SessionStore>()(
abortPromptSessionId: null,
abortPromptExpiresAt: null,
sessionStatus: new Map(),
sessionAttentionStates: new Map(),
userSummaryTitles: new Map(),
pendingInputText: null,
pendingInputMode: 'replace',
@@ -423,6 +424,26 @@ export const useSessionStore = create<SessionStore>()(
if (currentSessionId) {
setStatus(currentSessionId, 'busy');
const memoryState = get().sessionMemoryState.get(currentSessionId);
if (!memoryState || !memoryState.lastUserMessageAt) {
const currentMemoryState = get().sessionMemoryState;
const newMemoryState = new Map(currentMemoryState);
newMemoryState.set(currentSessionId, {
viewportAnchor: memoryState?.viewportAnchor ?? 0,
isStreaming: memoryState?.isStreaming ?? false,
lastAccessedAt: Date.now(),
backgroundMessageCount: memoryState?.backgroundMessageCount ?? 0,
lastUserMessageAt: Date.now(),
});
set({ sessionMemoryState: newMemoryState });
}
}
// Notify server that user sent a message in this session
if (currentSessionId) {
fetch(`/api/sessions/${currentSessionId}/message-sent`, { method: 'POST' })
.catch(() => { /* ignore */ });
}
try {