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
+22 -1
View File
@@ -8,6 +8,7 @@ import { getWorktreeStatus } from "@/lib/worktrees/worktreeStatus";
import { listProjectWorktrees, removeProjectWorktree } from "@/lib/worktrees/worktreeManager";
import { useDirectoryStore } from "./useDirectoryStore";
import { useProjectsStore } from "./useProjectsStore";
import { triggerSessionStatusPoll } from "@/hooks/useServerSessionStatus";
import type { ProjectEntry } from "@/lib/api/types";
import { checkIsGitRepository } from "@/lib/gitApi";
import { streamDebugEnabled } from "@/stores/utils/streamDebug";
@@ -1237,7 +1238,26 @@ export const useSessionStore = create<SessionStore>()(
},
setCurrentSession: (id: string | null) => {
const prevSessionId = get().currentSessionId;
set({ currentSessionId: id, error: null });
// Notify server of view state changes
// This enables server-side needs_attention tracking
if (prevSessionId && prevSessionId !== id) {
// Leaving previous session
fetch(`/api/sessions/${prevSessionId}/unview`, { method: 'POST' })
.catch(() => { /* ignore */ });
}
if (id) {
// Entering new session
fetch(`/api/sessions/${id}/view`, { method: 'POST' })
.catch(() => { /* ignore */ });
}
// Trigger immediate poll to get latest attention states
// This prevents stale state when switching sessions
triggerSessionStatusPoll();
const directory = opencodeClient.getDirectory() ?? null;
storeSessionForDirectory(directory, id);
},
@@ -1513,7 +1533,7 @@ export const useSessionStore = create<SessionStore>()(
const mergedSessions = dedupeSessionsById(persistedSessions);
return {
const mergedResult = {
...currentState,
...persistedState,
sessions: mergedSessions,
@@ -1527,6 +1547,7 @@ export const useSessionStore = create<SessionStore>()(
: currentState.availableWorktreesByProject,
lastLoadedDirectory,
};
return mergedResult;
},
}
),