Skip to content

feat(dashboard): #106 Phase B — ChatView planner UI#124

Merged
Abernaughty merged 2 commits intomainfrom
feat/106-chatview-planner-ui
Apr 5, 2026
Merged

feat(dashboard): #106 Phase B — ChatView planner UI#124
Abernaughty merged 2 commits intomainfrom
feat/106-chatview-planner-ui

Conversation

@Abernaughty
Copy link
Copy Markdown
Owner

@Abernaughty Abernaughty commented Apr 5, 2026

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)

File Purpose
stores/planner.svelte.ts Reactive Planner session store — manages phase state machine (idle → starting → chatting → sending → submitting → submitted), message history, checklist, TaskSpec
routes/api/planner/+server.ts SvelteKit proxy: POST /api/plannerPOST /tasks/plan
routes/api/planner/[id]/message/+server.ts SvelteKit proxy: POST /api/planner/{id}/messagePOST /tasks/plan/{id}/message
routes/api/planner/[id]/submit/+server.ts SvelteKit proxy: POST /api/planner/{id}/submitPOST /tasks/plan/{id}/submit

Modified Files (4)

File Changes
types/api.ts Added PlannerChecklistItem, PlannerChecklist, PlannerTaskSpec, PlannerSessionResponse, PlannerSubmitResponse, PlannerMessageEvent types. Added 'planner_message' to SSEEventType union.
sse.ts Added planner_message event listener + isPlannerMessageEvent type guard. Dispatches to plannerStore.handleSSE().
stores/index.ts Exports plannerStore. Added plannerStore.reset() to destroyAllStores().
components/views/ChatView.svelte Full rewrite. See UX flow below.

ChatView UX Flow

  1. Empty state — centered prompt explaining the Planner flow
  2. User types objective → Planner session starts automatically (workspace from WorkspaceSelector)
  3. Planner responds — validates against readiness checklist, prompts for missing required fields
  4. Inline checklist — live Task Readiness panel shows field status (✓/!/·), priority badges, auto-inferred markers, value previews
  5. Submit bar — green bar appears when required_satisfied === true with "SUBMIT TO ARCHITECT" button
  6. After submit — success message + "New Task" button to start fresh

Visual Elements

  • Phase indicator bar — shows current state with animated pulse during async operations
  • Message roles — User (cyan, right-aligned), Planner (purple, left), System (dim, left), Event (italic, muted)
  • Planner messages — line breaks preserved for readability
  • Warnings — rendered as event messages below Planner responses

Backend Contract

Maps to endpoints from PR #121 (src/agents/planner.py + src/api/main.py):

Dashboard Route Backend Route Purpose
POST /api/planner POST /tasks/plan Start session with workspace auto-inference
POST /api/planner/{id}/message POST /tasks/plan/{id}/message Exchange message with Planner LLM
POST /api/planner/{id}/submit POST /tasks/plan/{id}/submit Submit TaskSpec → create task → route to Architect

Testing Notes

  • Planner store is fully reactive using Svelte 5 $state runes
  • All error paths fall back to 'chatting' phase to allow retry
  • Workspace PIN forwarding works for protected workspaces
  • SSE planner_message handler includes session ID check to prevent cross-session contamination

Related Issues

Labels

component/dashboard, enhancement

Summary by CodeRabbit

  • New Features

    • Planner-driven chat flow with phase-aware status indicators and dynamic input/placeholder behavior.
    • Interactive checklist and readiness bar to guide session setup and task submission.
    • Real-time SSE-driven planner messages and warnings integrated into the chat.
  • Improvements

    • Smoother session lifecycle: start/send/submit flows with clearer enablement and submission handling.
    • Enhanced message styling and empty-state handling for clearer conversation UX.

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
@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Apr 5, 2026

📝 Walkthrough

Walkthrough

This PR adds a Planner-driven conversational task-creation flow: a new reactive plannerStore manages session lifecycle, messages, checklist, readiness and submission. ChatView is refactored to use the store. SSE, types, and SvelteKit API proxy routes are added to support Planner interactions and events.

Changes

Cohort / File(s) Summary
Chat UI Refactor
dashboard/src/lib/components/views/ChatView.svelte
Rewrote message orchestration to use plannerStore (replaces local messages/send state). UI now derives from plannerStore.messages, shows checklist, phase-based input/button states and labels, and gates input by workspace readiness.
Planner Store
dashboard/src/lib/stores/planner.svelte.ts
New reactive plannerStore implementing session lifecycle, phases, transcript (PlannerChatMessage), taskSpec and checklist. Exposes startSession, sendMessage, submit, handleSSE, reset, and computed getters (canSend, canSubmit, etc.). Uses request-generation tokens to ignore stale async results.
Store Barrel Integration
dashboard/src/lib/stores/index.ts
Exported plannerStore and added plannerStore.reset() into destroyAllStores() teardown.
SSE + Runtime Guarding
dashboard/src/lib/sse.ts, dashboard/src/lib/types/api.ts
Added 'planner_message' to SSEEventType, introduced PlannerMessageEvent and planner-related types (PlannerChecklistItem, PlannerChecklist, PlannerTaskSpec, PlannerSessionResponse, PlannerSubmitResponse). SSE dispatcher now validates payloads and forwards planner_message to plannerStore.handleSSE.
API Proxies (SvelteKit)
dashboard/src/routes/api/planner/+server.ts, dashboard/src/routes/api/planner/[id]/message/+server.ts, dashboard/src/routes/api/planner/[id]/submit/+server.ts
Three new POST endpoints that proxy requests to backend Planner endpoints (/tasks/plan, /tasks/plan/{id}/message, /tasks/plan/{id}/submit), normalize envelope { data, errors }, and propagate upstream HTTP statuses.

Sequence Diagram

sequenceDiagram
    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
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~55 minutes

Possibly related PRs

Poem

🐇 In burrows of code I hum and plot,

I gather checklists, stitch each thought,
From fuzzy asks to specs that gleam,
I hop—now tasks are ready to dream! ✨

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat(dashboard): #106 Phase B — ChatView planner UI' directly corresponds to the PR's main objective: implementing Phase B of the Planner agent as a ChatView planner UI component.
Linked Issues check ✅ Passed All Phase B backend objectives are met: Planner node logic (plannerStore), TaskSpec schema, API endpoints (POST /api/planner, /api/planner/{id}/message, /api/planner/{id}/submit), SSE planner_message events, and auto-inference support. All Phase B dashboard objectives are met: conversational flow, checklist display, phase indicator, role-based message styling, submit confirmation, and error handling.
Out of Scope Changes check ✅ Passed All code changes are scoped to Phase B objectives: planner store, API proxy endpoints, SSE planner_message routing, ChatView refactor, type definitions, and store exports. No Phase A (workspace selector) or unrelated changes are present.
Docstring Coverage ✅ Passed Docstring coverage is 85.71% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/106-chatview-planner-ui

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

📥 Commits

Reviewing files that changed from the base of the PR and between 9e3f90e and 48a7ee1.

📒 Files selected for processing (8)
  • dashboard/src/lib/components/views/ChatView.svelte
  • dashboard/src/lib/sse.ts
  • dashboard/src/lib/stores/index.ts
  • dashboard/src/lib/stores/planner.svelte.ts
  • dashboard/src/lib/types/api.ts
  • dashboard/src/routes/api/planner/+server.ts
  • dashboard/src/routes/api/planner/[id]/message/+server.ts
  • dashboard/src/routes/api/planner/[id]/submit/+server.ts

Comment on lines +13 to +22
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 });
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

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.

Suggested change
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)
Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 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

📥 Commits

Reviewing files that changed from the base of the PR and between 48a7ee1 and c7d7e6f.

📒 Files selected for processing (5)
  • dashboard/src/lib/components/views/ChatView.svelte
  • dashboard/src/lib/sse.ts
  • dashboard/src/lib/stores/planner.svelte.ts
  • dashboard/src/routes/api/planner/+server.ts
  • dashboard/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

@Abernaughty Abernaughty merged commit 1a284e3 into main Apr 5, 2026
3 checks passed
@Abernaughty Abernaughty deleted the feat/106-chatview-planner-ui branch April 5, 2026 21:05
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat: Planner agent + Chat task creation — readiness checklist, workspace selector, TaskSpec

1 participant