5 KiB
| phase | plan | subsystem | tags | dependency_graph | tech_stack | key_files | decisions | metrics | |||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 06-lab-advisor | 03 | frontend |
|
|
|
|
|
|
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 404fetchConversation(id)— GET /api/advisor/conversations/:idstreamChat(params, onToken, onDone, onError)— POST /api/advisor/chat, reads response.body viagetReader()+TextDecoder, splits on\n\nSSE delimiters, parsesdata:lines, callsonTokenper token andonDoneon[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:
streamingContentRefref pattern avoids stale closure in async SSE callbacks whilestreamingContentstate 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-autopadding on all pages. AdvisorPage needs a full-height flex container with no max-width for the two-panel sidebar+chat layout. - Fix: Added
noPadding?: booleanprop to AppShell. When true, rendersflex-1 flex flex-col overflow-hiddenwrapper. 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
- web/src/api/advisor.ts — created
- web/src/pages/AdvisorPage.tsx — created
- web/src/router.tsx — /advisor route added
- web/src/components/layout/TopBar.tsx — Advisor link added
- web/src/components/layout/AppShell.tsx — noPadding prop added
- Commit
811223d— feat(06-03): add advisor API wrappers - Commit
bcc3608— feat(06-03): add AdvisorPage, /advisor route, and TopBar link npm run build— exits 0, AdvisorPage-C_DruhTJ.js 6.72 kB in bundle