From 7679a1c7b95b8447cbdec76126e4dbdad53be4a4 Mon Sep 17 00:00:00 2001 From: Amit Kumar Date: Sat, 16 May 2026 13:12:46 +0000 Subject: [PATCH] fix(web): session history empty + agents never highlighted MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Three UX gaps surfaced in the live app after rc2: 1. SessionsRail was empty whenever no session was actively running. useSessionList was calling GET /api/v1/sessions (in-flight only), not GET /api/v1/sessions/recent (history + in-flight). Backend docstring confirms: /sessions returns "snapshot of in-flight sessions"; /sessions/recent returns "any status — closed + active". Switched useSessionList to /sessions/recent?limit=50; the same cross-session SSE still pushes live status_changed / agent_running deltas onto the rendered list. 2. FlowStrip nodes were always "IDLE" because App.tsx passed activeAgent={null} hardcoded. Now derive it from the current session's active_agent field (set by the cross-session SSE's session.agent_running event) — when set, FlowStrip's asr-flow-halo breathes on that node, giving the "X is thinking" feedback the user was missing. 3. No history of which agents had already run on a session. Derive statusByAgent from sessionFull.state.agentsRun: any agent that appears in the run log marks 'done' (filled background); when the session terminates in 'error' and there's no current active agent, the last run flips to 'error'. The flow strip now visibly shows the pipeline's progress at a glance. Files: - web/src/state/useSessionList.ts: /sessions -> /sessions/recent?limit=50 - web/src/App.tsx: derive activeAgent + statusByAgent and pass to FlowStrip Verified locally: npm run typecheck clean, vitest 48 files / 196 tests pass, npm run build 321 kB / 98 kB gzip. --- web/src/App.tsx | 30 +++++++++++++++++++++++++++--- web/src/state/useSessionList.ts | 6 +++++- 2 files changed, 32 insertions(+), 4 deletions(-) diff --git a/web/src/App.tsx b/web/src/App.tsx index 43072ac..5ad8106 100644 --- a/web/src/App.tsx +++ b/web/src/App.tsx @@ -1,9 +1,9 @@ -import { useState } from 'react'; +import { useMemo, useState } from 'react'; import type { CSSProperties } from 'react'; import { Topbar, type Health } from '@/shell/Topbar'; import { Statusbar, type ConnectionState, type VmSeqState } from '@/shell/Statusbar'; import { SessionsRail } from '@/shell/SessionsRail'; -import { FlowStrip } from '@/shell/FlowStrip'; +import { FlowStrip, type NodeStatus } from '@/shell/FlowStrip'; import { SessionCanvas } from '@/canvas/SessionCanvas'; import { useUiHints } from '@/state/useUiHints'; import { useSessionList } from '@/state/useSessionList'; @@ -62,6 +62,29 @@ export function App() { const vmSeqState: VmSeqState = 'in-sync'; const vmSeq = sessionFull.state.vmSeq; + // FlowStrip feedback: active agent (live) + statusByAgent (history). + // Sourced from the cross-session SSE that drives sessionList.sessions — + // the active row's active_agent field updates in real time via + // session.agent_running deltas; agents that appear in agents_run on + // the selected session are marked 'done'; the last one flips to 'error' + // when the session terminates in an error state. + const activeSession = activeSid + ? sessionList.sessions.find((s) => s.id === activeSid) + : null; + const activeAgent = activeSession?.active_agent ?? null; + const statusByAgent = useMemo>(() => { + const m: Record = {}; + for (const run of sessionFull.state.agentsRun) { + m[run.agent] = 'done'; + } + const sess = sessionFull.state.session; + if (sess?.status === 'error' && !activeAgent) { + const last = sessionFull.state.agentsRun.at(-1); + if (last) m[last.agent] = 'error'; + } + return m; + }, [sessionFull.state.agentsRun, sessionFull.state.session, activeAgent]); + return (
{breakpoint === 'mobile' ? ( { let cancelled = false; - apiFetch('/sessions') + // Hit /sessions/recent (history) instead of /sessions (in-flight only) + // so the rail shows past sessions even when nothing is currently + // running. The cross-session SSE below still pushes live status + + // agent_running deltas onto the same list. + apiFetch('/sessions/recent?limit=50') .then((list) => { if (cancelled) return; setSessions(list);