# Flowchart: viewer-ui-layer ## Sources Consulted - `src/ui/viewer/App.tsx:1-162` - `src/ui/viewer/index.tsx:1-16` - `src/ui/viewer/hooks/useSSE.ts:1-147` - `src/ui/viewer/hooks/useSettings.ts:1-80` - `src/ui/viewer/hooks/usePagination.ts:1-80` - `src/ui/viewer/types.ts:1-80` - `src/ui/viewer/components/Header.tsx:1-60` - `src/ui/viewer/components/Feed.tsx:1-60` - `src/ui/viewer/components/ObservationCard.tsx:1-60` - `src/ui/viewer/components/ErrorBoundary.tsx:1-63` - `src/ui/viewer/components/ContextSettingsModal.tsx:1-60` - `src/services/worker/SSEBroadcaster.ts:1-77` - `src/services/worker/http/routes/ViewerRoutes.ts` ## Component Tree 1. ErrorBoundary (root) 2. App (orchestrator) 3. Header — project/source filters, SSE status, theme toggle 4. Feed — interleaved cards, infinite scroll via IntersectionObserver 5. ObservationCard / SummaryCard / PromptCard 6. ContextSettingsModal 7. LogsDrawer ## Happy Path Description User loads `http://localhost:37777` → static viewer.html served → React mounts via `index.tsx` → `` → App initializes hooks (`useSSE`, `useSettings`, `useTheme`, `usePagination`, `useStats`) → `useSSE` opens `EventSource('/stream')` → backend emits `initial_load` with catalog → Header + Feed render → IntersectionObserver triggers `handleLoadMore` on scroll → `pagination.*.loadMore()` fetches `/api/observations?offset=X&limit=20` → merged with live SSE data in `useMemo` (deduped by `(project, id)`) → re-render. Real-time events (`new_observation`, `new_summary`, `new_prompt`) update state → re-render. Settings modal saves via `POST /api/settings`. ## Mermaid Flowchart ```mermaid flowchart TD HTTP["GET /
ViewerRoutes.ts"] --> EB["ErrorBoundary
index.tsx:4"] EB --> APP["App
App.tsx:14"] APP --> SSE["useSSE
useSSE.ts:6"] APP --> SETTINGS["useSettings
useSettings.ts:8"] APP --> PAGINATION["usePagination
usePagination.ts:18"] APP --> THEME["useTheme"] APP --> STATS["useStats"] SSE -->|EventSource| STREAM["/stream
ViewerRoutes.handleSSEStream"] STREAM --> BROADCASTER["SSEBroadcaster
SSEBroadcaster.ts:15"] BROADCASTER --> SSE APP --> HEADER["Header
Header.tsx:34"] APP --> FEED["Feed
Feed.tsx:18"] APP --> MODAL["ContextSettingsModal"] APP --> LOGS["LogsDrawer"] HEADER --> FilterState[(currentFilter
currentSource)] FEED -->|IntersectionObserver| LoadMore["handleLoadMore"] LoadMore --> PAGINATION PAGINATION -->|GET /api/observations?offset=X| API_OBS["DataRoutes"] FEED --> OBS["ObservationCard
ObservationCard.tsx:33"] FEED --> SUM["SummaryCard"] FEED --> PRO["PromptCard"] MODAL -->|POST /api/settings| API_SET["SettingsRoutes"] ``` ## State Management Hooks + local state; no Redux/Zustand/Context store. - `useSSE`: observations, summaries, prompts, catalog, isConnected, isProcessing, queueDepth. EventSource events update. - `useSettings`: settings object, isSaving, saveStatus. - `usePagination`: per-datatype isLoading, hasMore, offsetRef, lastSelectionRef. Resets offset on filter change. - `useTheme`: preference, applies to DOM. - `useStats`: stats fetched once. - App local: `currentFilter`, `currentSource`, `contextPreviewOpen`, `logsModalOpen`, `paginatedObservations/Summaries/Prompts`. **Duplication note:** Observations live in both `useSSE().observations` (live) and App's `paginatedObservations` (older chunks). Merged in `useMemo` with `(project, id)` dedup. ## Side Effects - EventSource auto-reconnect on error after `TIMING.SSE_RECONNECT_DELAY_MS`. - IntersectionObserver setup/cleanup per Feed mount. - Fetch settings + stats on mount. - DOM theme attribute mutation. ## External Feature Dependencies **Consumes:** SSEBroadcaster (backend SSE), DataRoutes (pagination), SettingsRoutes (config), SessionStore (catalog on init). ## Confidence + Gaps **High:** SSE flow; hook composition; pagination; state merging. **Medium:** Exact paginated response shape; catalog-update strategy (additive only). **Gaps:** CSS layer; `TerminalPreview`, `ThemeToggle`, `GitHubStarsButton`; full LogsModal console capture; saveSettings error branch.