--- phase: 06-lab-advisor plan: "03" subsystem: frontend tags: [advisor, chat, streaming, sse, react, tanstack-query] dependency_graph: requires: [06-02] provides: [advisor-ui, streaming-chat-frontend] affects: [web/src/router.tsx, web/src/components/layout/TopBar.tsx, web/src/components/layout/AppShell.tsx] tech_stack: added: [] patterns: [ReadableStream SSE parsing, TanStack Query refetchInterval sidebar, optimistic user messages, streaming cursor animation] key_files: created: - web/src/api/advisor.ts - web/src/pages/AdvisorPage.tsx modified: - web/src/router.tsx - web/src/components/layout/TopBar.tsx - web/src/components/layout/AppShell.tsx decisions: - "Used fetch + ReadableStream instead of EventSource for SSE — POST body required for chat endpoint" - "Used a ref (streamingContentRef) alongside state to capture latest streaming content inside async callbacks without stale closure issues" - "Added noPadding prop to AppShell to support full-height two-panel layout without max-width constraint" - "Model label in sidebar shows only the model slug (after /) to keep sidebar compact" metrics: duration: "~8 minutes" completed: "2026-04-10T07:39:59Z" tasks_completed: 2 files_changed: 5 --- # Phase 06 Plan 03: AdvisorPage Frontend Summary **One-liner:** Two-panel streaming chat UI at /advisor — sidebar conversation list, fetch+ReadableStream SSE token streaming, model dropdown, ClickHouse dark theme. ## Tasks Completed | # | Task | Commit | Files | |---|------|--------|-------| | 1 | API wrappers for advisor endpoints | 811223d | web/src/api/advisor.ts | | 2 | AdvisorPage component, route, and TopBar link | bcc3608 | web/src/pages/AdvisorPage.tsx, web/src/router.tsx, web/src/components/layout/TopBar.tsx, web/src/components/layout/AppShell.tsx | ## What Was Built ### web/src/api/advisor.ts Typed API wrappers for all three advisor endpoints: - `fetchConversations()` — GET /api/advisor/conversations, returns `[]` on 404 - `fetchConversation(id)` — GET /api/advisor/conversations/:id - `streamChat(params, onToken, onDone, onError)` — POST /api/advisor/chat, reads response.body via `getReader()` + `TextDecoder`, splits on `\n\n` SSE delimiters, parses `data:` lines, calls `onToken` per token and `onDone` on `[DONE]` ### web/src/pages/AdvisorPage.tsx Full two-panel AdvisorPage: - Left sidebar (280px, hidden on mobile): "Lab Advisor" heading, "New Chat" button, conversation list with `refetchInterval: 5000`, selected state highlighted - Main panel: messages area (scrolls to bottom on new content), optimistic user messages (right-aligned bg-[#1a1a1a]), assistant messages (left-aligned bg-[#111]), streaming assistant turn with animated cursor - Input row: model `