docs(06-03): complete AdvisorPage plan — streaming chat UI, /advisor route, TopBar link

This commit is contained in:
Mikkel Georgsen 2026-04-10 07:40:39 +00:00
parent bcc360892c
commit f34cf401a0

View file

@ -0,0 +1,96 @@
---
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 `<select>` (4 OpenRouter models), textarea (Enter to send, Shift+Enter for newline), Send button disabled while streaming or empty
- State: `streamingContentRef` ref pattern avoids stale closure in async SSE callbacks while `streamingContent` state drives render
### web/src/router.tsx
Lazy `AdvisorPage` import + `advisorRoute` at path `/advisor` added to route tree.
### web/src/components/layout/TopBar.tsx
`MessageSquare` icon + Advisor `Button variant="outline"` link inserted between Test and Scan buttons.
### web/src/components/layout/AppShell.tsx
Added `noPadding?: boolean` prop — when true, wraps children in `flex-1 flex flex-col overflow-hidden` instead of the default padded/max-width container. Required for AdvisorPage's full-viewport two-panel layout.
## Deviations from Plan
### Auto-fixed Issues
**1. [Rule 2 - Missing Feature] AppShell noPadding prop**
- **Found during:** Task 2
- **Issue:** AppShell forced `px-4 py-6 max-w-7xl mx-auto` padding on all pages. AdvisorPage needs a full-height flex container with no max-width for the two-panel sidebar+chat layout.
- **Fix:** Added `noPadding?: boolean` prop to AppShell. When true, renders `flex-1 flex flex-col overflow-hidden` wrapper. Backward compatible — all existing pages unaffected.
- **Files modified:** web/src/components/layout/AppShell.tsx
- **Commit:** bcc3608
## Known Stubs
None — all data flows are wired to real backend endpoints from plan 06-02. Conversations and messages load from the API; streaming connects to POST /api/advisor/chat.
## Threat Surface Scan
No new network endpoints or auth paths introduced in this plan. All frontend calls go to existing backend endpoints established in 06-02. Assistant message tokens rendered as React text nodes (JSX children) — no `dangerouslySetInnerHTML` used anywhere. T-06-03-01 (XSS) mitigated.
## Self-Check: PASSED
- [x] web/src/api/advisor.ts — created
- [x] web/src/pages/AdvisorPage.tsx — created
- [x] web/src/router.tsx — /advisor route added
- [x] web/src/components/layout/TopBar.tsx — Advisor link added
- [x] web/src/components/layout/AppShell.tsx — noPadding prop added
- [x] Commit 811223d — feat(06-03): add advisor API wrappers
- [x] Commit bcc3608 — feat(06-03): add AdvisorPage, /advisor route, and TopBar link
- [x] `npm run build` — exits 0, AdvisorPage-C_DruhTJ.js 6.72 kB in bundle