feat(dashboard): #106 Phase B — ChatView planner UI#124
Conversation
Wire the dashboard ChatView to the Planner backend (PR #121). Conversational task planning flow: workspace select → describe objective → Planner validates checklist → submit to Architect. New files: - stores/planner.svelte.ts — session state, message history, checklist - routes/api/planner/+server.ts — proxy POST /tasks/plan - routes/api/planner/[id]/message/+server.ts — proxy message endpoint - routes/api/planner/[id]/submit/+server.ts — proxy submit endpoint Modified files: - types/api.ts — Planner types + planner_message SSE event - sse.ts — dispatch planner_message events to plannerStore - stores/index.ts — export + teardown plannerStore - components/views/ChatView.svelte — full rewrite with Planner flow Closes #106
📝 WalkthroughWalkthroughThis PR adds a Planner-driven conversational task-creation flow: a new reactive Changes
Sequence DiagramsequenceDiagram
participant User
participant ChatView
participant PlannerStore
participant Backend
participant SSE
rect rgba(200,220,255,0.5)
User->>ChatView: Type objective / click "New Task"
ChatView->>PlannerStore: startSession(workspace, options)
PlannerStore->>Backend: POST /api/planner
Backend-->>PlannerStore: PlannerSessionResponse (session_id, message, checklist, ready)
PlannerStore->>ChatView: update messages, checklist, phase
end
rect rgba(200,255,200,0.5)
User->>ChatView: Reply with missing info
ChatView->>PlannerStore: sendMessage(text)
PlannerStore->>PlannerStore: append user message, set phase=sending
PlannerStore->>Backend: POST /api/planner/{id}/message
Backend-->>SSE: planner_message event (session_id, message, ready, warnings)
SSE->>PlannerStore: handleSSE(payload)
PlannerStore->>ChatView: update messages, ready, warnings
end
rect rgba(255,230,200,0.5)
User->>ChatView: Click "Submit to Architect"
ChatView->>PlannerStore: submit()
PlannerStore->>Backend: POST /api/planner/{id}/submit
Backend-->>PlannerStore: PlannerSubmitResponse (task_id, status)
PlannerStore->>ChatView: update phase=submitted, submittedTaskId
ChatView->>User: show confirmation / link
end
Estimated code review effort🎯 4 (Complex) | ⏱️ ~55 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Actionable comments posted: 4
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@dashboard/src/lib/components/views/ChatView.svelte`:
- Around line 99-130: The code clears the input draft before attempting
plannerStore.startSession, causing the user prompt to be lost if startSession
fails; update handleSend so input is only cleared after a successful session
start (i.e., after plannerStore.startSession returns true) and/or save the
original draft (e.g., const draft = input) and restore it on failure by setting
input = draft; specifically, move or guard the input = '' line so it runs after
the started check (or restore on catch/error), touching handleSend,
plannerStore.startSession, and plannerStore.sendMessage call sites referenced in
the diff.
In `@dashboard/src/lib/sse.ts`:
- Around line 59-69: The isPlannerMessageEvent type guard currently misses
validation for the required warnings field; update the guard (function
isPlannerMessageEvent) to also verify payload.warnings is an array of strings
(e.g., Array.isArray(payload.warnings) && payload.warnings.every(item => typeof
item === 'string')) so PlannerMessageEvent consumers like plannerStore.handleSSE
can safely access warnings as string[].
In `@dashboard/src/lib/stores/planner.svelte.ts`:
- Around line 103-154: The async startSession method can apply awaited fetch
results after reset() clears state; add a stale-response guard (e.g. a requestId
or sessionToken property on the planner store) that reset() increments/clears
and that startSession checks immediately after each await (before calling
applySession, setting messages, phase, error, etc.); apply the same pattern to
the other async methods referenced (submitTask, and the other async actions in
the planner store) or alternatively use an AbortController that reset() aborts
and the fetch awaits check for aborts before mutating state.
In `@dashboard/src/routes/api/planner/`+server.ts:
- Around line 13-22: The POST request handler's call to request.json() can throw
on malformed input and currently results in a 500; wrap the JSON parsing in a
try/catch inside the exported POST RequestHandler and return a 400 JSON response
when parsing fails (include a clear error payload, e.g., data:null and an errors
array with a message about invalid JSON) instead of letting the exception
bubble; apply the same guard to the analogous handler in
planner/[id]/message/+server.ts so both endpoints normalize malformed JSON to
HTTP 400.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 814f9a12-011a-45a1-a4d7-d487301fe638
📒 Files selected for processing (8)
dashboard/src/lib/components/views/ChatView.sveltedashboard/src/lib/sse.tsdashboard/src/lib/stores/index.tsdashboard/src/lib/stores/planner.svelte.tsdashboard/src/lib/types/api.tsdashboard/src/routes/api/planner/+server.tsdashboard/src/routes/api/planner/[id]/message/+server.tsdashboard/src/routes/api/planner/[id]/submit/+server.ts
| export const POST: RequestHandler = async ({ request }) => { | ||
| const body = await request.json(); | ||
| const result = await apiFetch<PlannerSessionResponse>('/tasks/plan', { | ||
| method: 'POST', | ||
| body: JSON.stringify(body) | ||
| }); | ||
| if (!result.ok) { | ||
| return json({ data: null, errors: result.errors }, { status: result.status }); | ||
| } | ||
| return json({ data: result.data, errors: [] }, { status: 201 }); |
There was a problem hiding this comment.
Treat malformed JSON as a 400, not a 500.
request.json() throws on invalid bodies, so this handler currently turns bad input into an unhandled 500. Other POST proxies already normalize that case to a 400, and the same guard should be mirrored in dashboard/src/routes/api/planner/[id]/message/+server.ts.
💡 Suggested fix
export const POST: RequestHandler = async ({ request }) => {
- const body = await request.json();
+ let body: unknown;
+ try {
+ body = await request.json();
+ } catch {
+ return json({ data: null, errors: ['Invalid JSON body'] }, { status: 400 });
+ }
const result = await apiFetch<PlannerSessionResponse>('/tasks/plan', {
method: 'POST',
body: JSON.stringify(body)
});📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| export const POST: RequestHandler = async ({ request }) => { | |
| const body = await request.json(); | |
| const result = await apiFetch<PlannerSessionResponse>('/tasks/plan', { | |
| method: 'POST', | |
| body: JSON.stringify(body) | |
| }); | |
| if (!result.ok) { | |
| return json({ data: null, errors: result.errors }, { status: result.status }); | |
| } | |
| return json({ data: result.data, errors: [] }, { status: 201 }); | |
| export const POST: RequestHandler = async ({ request }) => { | |
| let body: unknown; | |
| try { | |
| body = await request.json(); | |
| } catch { | |
| return json({ data: null, errors: ['Invalid JSON body'] }, { status: 400 }); | |
| } | |
| const result = await apiFetch<PlannerSessionResponse>('/tasks/plan', { | |
| method: 'POST', | |
| body: JSON.stringify(body) | |
| }); | |
| if (!result.ok) { | |
| return json({ data: null, errors: result.errors }, { status: result.status }); | |
| } | |
| return json({ data: result.data, errors: [] }, { status: 201 }); |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@dashboard/src/routes/api/planner/`+server.ts around lines 13 - 22, The POST
request handler's call to request.json() can throw on malformed input and
currently results in a 500; wrap the JSON parsing in a try/catch inside the
exported POST RequestHandler and return a 400 JSON response when parsing fails
(include a clear error payload, e.g., data:null and an errors array with a
message about invalid JSON) instead of letting the exception bubble; apply the
same guard to the analogous handler in planner/[id]/message/+server.ts so both
endpoints normalize malformed JSON to HTTP 400.
1. ChatView: defer input clearing until after startSession succeeds (CodeRabbit Major #1 — user text preserved on failure) 2. sse.ts: add warnings array validation to isPlannerMessageEvent (CodeRabbit Minor #2 — full type guard coverage) 3. planner.svelte.ts: add requestGeneration stale-response guard across all async methods + reset() invalidation (CodeRabbit Major #3 — prevents race on navigation/reset) 4. planner proxy routes: try/catch request.json() → 400 on malformed (CodeRabbit Minor #4 — both +server.ts and [id]/message/+server.ts)
There was a problem hiding this comment.
🧹 Nitpick comments (1)
dashboard/src/lib/stores/planner.svelte.ts (1)
219-227: Consider batching warning message additions.Each loop iteration creates a new messages array. For multiple warnings, this is inefficient.
♻️ Proposed batch update
// Add warnings as event messages if (resp.warnings?.length) { - for (const w of resp.warnings) { - messages = [...messages, { + messages = [ + ...messages, + ...resp.warnings.map(w => ({ role: 'event', text: w, time: nowTime() - }]; - } + } as PlannerChatMessage)) + ]; }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@dashboard/src/lib/stores/planner.svelte.ts` around lines 219 - 227, The current loop repeatedly reassigns messages inside the for loop which is inefficient; instead collect all warning message objects first (using resp.warnings and nowTime()) into a temporary array and then append them in one update to messages (e.g., one spread or push using messages = [...messages, ...newWarnings]) so you only perform a single reassignment/update of the messages variable and preserve role: 'event' and text from each w.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Nitpick comments:
In `@dashboard/src/lib/stores/planner.svelte.ts`:
- Around line 219-227: The current loop repeatedly reassigns messages inside the
for loop which is inefficient; instead collect all warning message objects first
(using resp.warnings and nowTime()) into a temporary array and then append them
in one update to messages (e.g., one spread or push using messages =
[...messages, ...newWarnings]) so you only perform a single reassignment/update
of the messages variable and preserve role: 'event' and text from each w.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 16ad015f-5008-4428-8674-8de71e0f390a
📒 Files selected for processing (5)
dashboard/src/lib/components/views/ChatView.sveltedashboard/src/lib/sse.tsdashboard/src/lib/stores/planner.svelte.tsdashboard/src/routes/api/planner/+server.tsdashboard/src/routes/api/planner/[id]/message/+server.ts
✅ Files skipped from review due to trivial changes (1)
- dashboard/src/lib/components/views/ChatView.svelte
🚧 Files skipped from review as they are similar to previous changes (3)
- dashboard/src/lib/sse.ts
- dashboard/src/routes/api/planner/+server.ts
- dashboard/src/routes/api/planner/[id]/message/+server.ts
Summary
Wires the dashboard ChatView to the Planner backend (PR #121). Replaces the direct
tasksStore.create()flow with a conversational Planner session that validates task completeness before routing to the Architect.What Changed
New Files (4)
stores/planner.svelte.tsidle → starting → chatting → sending → submitting → submitted), message history, checklist, TaskSpecroutes/api/planner/+server.tsPOST /api/planner→POST /tasks/planroutes/api/planner/[id]/message/+server.tsPOST /api/planner/{id}/message→POST /tasks/plan/{id}/messageroutes/api/planner/[id]/submit/+server.tsPOST /api/planner/{id}/submit→POST /tasks/plan/{id}/submitModified Files (4)
types/api.tsPlannerChecklistItem,PlannerChecklist,PlannerTaskSpec,PlannerSessionResponse,PlannerSubmitResponse,PlannerMessageEventtypes. Added'planner_message'toSSEEventTypeunion.sse.tsplanner_messageevent listener +isPlannerMessageEventtype guard. Dispatches toplannerStore.handleSSE().stores/index.tsplannerStore. AddedplannerStore.reset()todestroyAllStores().components/views/ChatView.svelteChatView UX Flow
required_satisfied === truewith "SUBMIT TO ARCHITECT" buttonVisual Elements
Backend Contract
Maps to endpoints from PR #121 (
src/agents/planner.py+src/api/main.py):POST /api/plannerPOST /tasks/planPOST /api/planner/{id}/messagePOST /tasks/plan/{id}/messagePOST /api/planner/{id}/submitPOST /tasks/plan/{id}/submitTesting Notes
$staterunes'chatting'phase to allow retryplanner_messagehandler includes session ID check to prevent cross-session contaminationRelated Issues
Labels
component/dashboard,enhancementSummary by CodeRabbit
New Features
Improvements