[nexus] fix(22): revise plans based on checker feedback
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
1b64970e90
commit
e8c70d6c8d
4 changed files with 118 additions and 80 deletions
|
|
@ -53,10 +53,17 @@ must_haves:
|
||||||
---
|
---
|
||||||
|
|
||||||
<objective>
|
<objective>
|
||||||
Server-side streaming infrastructure: DB schema additions for message editing, SSE streaming endpoint for LLM token delivery, message edit route, agent selection on conversations, and server tests.
|
Server-side streaming infrastructure: DB schema additions for message editing, SSE streaming endpoint with echo-stream placeholder, message edit route, agent selection on conversations, and server tests.
|
||||||
|
|
||||||
Purpose: Establishes the entire server-side API surface that the UI plans (02/03) will consume. Every new endpoint is tested.
|
Purpose: Establishes the entire server-side API surface that the UI plans (02/03) will consume. Every new endpoint is tested.
|
||||||
Output: Working SSE stream endpoint, edit message endpoint, conversation agent update, migration SQL, tests.
|
Output: Working SSE stream endpoint (echo-stream mode), edit message endpoint, conversation agent update, migration SQL, tests.
|
||||||
|
|
||||||
|
NOTE -- Echo-stream scope (CHAT-01 partial): The SSE endpoint uses an echo-stream that replays the
|
||||||
|
user's last message word-by-word. This fully exercises the streaming pipeline (SSE headers, token
|
||||||
|
events, done event, abort detection, message persistence) so the UI can be built and tested against
|
||||||
|
real streaming behavior. Real LLM integration (replacing the echo loop with an adapter call) is
|
||||||
|
Phase 23 (Brainstormer agent). The echo-stream satisfies CHAT-01's "tokens appear as generated"
|
||||||
|
contract at the transport level; Phase 23 provides semantic content.
|
||||||
</objective>
|
</objective>
|
||||||
|
|
||||||
<execution_context>
|
<execution_context>
|
||||||
|
|
@ -177,30 +184,30 @@ res.write(":ok\n\n");
|
||||||
- Test: PUT /conversations/:id/messages/:messageId with { content: "new" } returns 200 with editedContent set
|
- Test: PUT /conversations/:id/messages/:messageId with { content: "new" } returns 200 with editedContent set
|
||||||
</behavior>
|
</behavior>
|
||||||
<action>
|
<action>
|
||||||
1. **DB schema** — Add two columns to `chatMessages` in `packages/db/src/schema/chat_messages.ts`:
|
1. **DB schema** -- Add two columns to `chatMessages` in `packages/db/src/schema/chat_messages.ts`:
|
||||||
```typescript
|
```typescript
|
||||||
editedContent: text("edited_content"),
|
editedContent: text("edited_content"),
|
||||||
editedAt: timestamp("edited_at", { withTimezone: true }),
|
editedAt: timestamp("edited_at", { withTimezone: true }),
|
||||||
```
|
```
|
||||||
Then run `pnpm db:generate` to create the migration SQL.
|
Then run `pnpm db:generate` to create the migration SQL.
|
||||||
|
|
||||||
2. **Shared types** — Update `ChatMessage` interface in `packages/shared/src/types/chat.ts`:
|
2. **Shared types** -- Update `ChatMessage` interface in `packages/shared/src/types/chat.ts`:
|
||||||
- Add `editedContent: string | null;`
|
- Add `editedContent: string | null;`
|
||||||
- Add `editedAt: string | null;`
|
- Add `editedAt: string | null;`
|
||||||
|
|
||||||
3. **Validators** — In `packages/shared/src/validators/chat.ts`:
|
3. **Validators** -- In `packages/shared/src/validators/chat.ts`:
|
||||||
- Update `updateConversationSchema` to include `agentId: z.string().uuid().optional().nullable()`
|
- Update `updateConversationSchema` to include `agentId: z.string().uuid().optional().nullable()`
|
||||||
- Add `export const editMessageSchema = z.object({ content: z.string().min(1) });`
|
- Add `export const editMessageSchema = z.object({ content: z.string().min(1) });`
|
||||||
- Add `export const streamMessageSchema = z.object({ content: z.string().min(1), agentId: z.string().uuid().optional().nullable() });`
|
- Add `export const streamMessageSchema = z.object({ content: z.string().min(1), agentId: z.string().uuid().optional().nullable() });`
|
||||||
|
|
||||||
4. **Service methods** — Add to `chatService` in `server/src/services/chat.ts`:
|
4. **Service methods** -- Add to `chatService` in `server/src/services/chat.ts`:
|
||||||
- `editMessage(messageId: string, data: { content: string })` — sets `editedContent = data.content`, `editedAt = new Date()` on the message row, returns the updated row
|
- `editMessage(messageId: string, data: { content: string })` -- sets `editedContent = data.content`, `editedAt = new Date()` on the message row, returns the updated row
|
||||||
- `getMessageHistory(conversationId: string)` — selects all messages WHERE conversationId matches, ORDER BY createdAt ASC (ascending, for LLM context window). Returns `ChatMessage[]`. Use `editedContent ?? content` as the effective content field (alias as `effectiveContent` in the return).
|
- `getMessageHistory(conversationId: string)` -- selects all messages WHERE conversationId matches, ORDER BY createdAt ASC (ascending, for LLM context window). Returns `ChatMessage[]`. Use `editedContent ?? content` as the effective content field (alias as `effectiveContent` in the return).
|
||||||
- Update `updateConversation` to accept and persist `agentId` field: `set({ title: data.title, agentId: data.agentId, updatedAt: new Date() })`. Only set fields that are provided (check `data.agentId !== undefined` before including in set).
|
- Update `updateConversation` to accept and persist `agentId` field: `set({ title: data.title, agentId: data.agentId, updatedAt: new Date() })`. Only set fields that are provided (check `data.agentId !== undefined` before including in set).
|
||||||
|
|
||||||
5. **Extend existing tests** in `server/src/__tests__/chat-routes.test.ts`:
|
5. **Extend existing tests** in `server/src/__tests__/chat-routes.test.ts`:
|
||||||
- Add test: `PATCH /conversations/:id with agentId` — create conversation, PATCH with `{ agentId: someAgentId }`, verify response has the agentId set. (Use a dummy UUID string for agentId if the test DB doesn't enforce FK — check existing test patterns.)
|
- Add test: `PATCH /conversations/:id with agentId` -- create conversation, PATCH with `{ agentId: someAgentId }`, verify response has the agentId set. (Use a dummy UUID string for agentId if the test DB doesn't enforce FK -- check existing test patterns.)
|
||||||
- Add test: `PUT /conversations/:id/messages/:messageId` — create conversation, add message, PUT with `{ content: "edited" }`, verify response has `editedContent: "edited"` and `editedAt` is not null.
|
- Add test: `PUT /conversations/:id/messages/:messageId` -- create conversation, add message, PUT with `{ content: "edited" }`, verify response has `editedContent: "edited"` and `editedAt` is not null.
|
||||||
</action>
|
</action>
|
||||||
<verify>
|
<verify>
|
||||||
<automated>pnpm --filter @paperclipai/server test run -- --reporter=verbose chat-routes</automated>
|
<automated>pnpm --filter @paperclipai/server test run -- --reporter=verbose chat-routes</automated>
|
||||||
|
|
@ -221,7 +228,7 @@ res.write(":ok\n\n");
|
||||||
</task>
|
</task>
|
||||||
|
|
||||||
<task type="auto" tdd="true">
|
<task type="auto" tdd="true">
|
||||||
<name>Task 2: SSE streaming endpoint + edit message route + stream tests</name>
|
<name>Task 2: SSE echo-stream endpoint + edit message route + stream tests</name>
|
||||||
<files>
|
<files>
|
||||||
server/src/routes/chat.ts,
|
server/src/routes/chat.ts,
|
||||||
server/src/__tests__/chat-stream-routes.test.ts
|
server/src/__tests__/chat-stream-routes.test.ts
|
||||||
|
|
@ -241,7 +248,13 @@ res.write(":ok\n\n");
|
||||||
- Test: Client close (req.destroy()) stops the stream loop
|
- Test: Client close (req.destroy()) stops the stream loop
|
||||||
</behavior>
|
</behavior>
|
||||||
<action>
|
<action>
|
||||||
1. **Edit message route** — Add to `server/src/routes/chat.ts`:
|
**IMPORTANT -- Echo-stream placeholder:** This task implements an echo-stream (replays the user's
|
||||||
|
last message word-by-word) as a functional placeholder. This is intentional -- it fully exercises
|
||||||
|
the SSE pipeline so the UI (Plans 02/03) can develop against real streaming behavior. Phase 23
|
||||||
|
will replace the echo loop body with real LLM adapter calls. The SSE contract (token events,
|
||||||
|
done event, abort detection, message persistence) is the deliverable here, not LLM content.
|
||||||
|
|
||||||
|
1. **Edit message route** -- Add to `server/src/routes/chat.ts`:
|
||||||
```typescript
|
```typescript
|
||||||
// PUT /conversations/:id/messages/:messageId
|
// PUT /conversations/:id/messages/:messageId
|
||||||
router.put("/conversations/:id/messages/:messageId", validate(editMessageSchema), async (req, res) => {
|
router.put("/conversations/:id/messages/:messageId", validate(editMessageSchema), async (req, res) => {
|
||||||
|
|
@ -255,7 +268,7 @@ res.write(":ok\n\n");
|
||||||
});
|
});
|
||||||
```
|
```
|
||||||
|
|
||||||
2. **SSE stream endpoint** — Add to `server/src/routes/chat.ts`:
|
2. **SSE stream endpoint** -- Add to `server/src/routes/chat.ts`:
|
||||||
```typescript
|
```typescript
|
||||||
// GET /conversations/:id/stream
|
// GET /conversations/:id/stream
|
||||||
router.get("/conversations/:id/stream", async (req, res) => {
|
router.get("/conversations/:id/stream", async (req, res) => {
|
||||||
|
|
@ -269,7 +282,7 @@ res.write(":ok\n\n");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set SSE headers — copied from plugins.ts:1146
|
// Set SSE headers -- copied from plugins.ts:1146
|
||||||
res.writeHead(200, {
|
res.writeHead(200, {
|
||||||
"Content-Type": "text/event-stream",
|
"Content-Type": "text/event-stream",
|
||||||
"Cache-Control": "no-cache",
|
"Cache-Control": "no-cache",
|
||||||
|
|
@ -288,10 +301,11 @@ res.write(":ok\n\n");
|
||||||
// Get message history for LLM context
|
// Get message history for LLM context
|
||||||
const history = await svc.getMessageHistory(conversationId);
|
const history = await svc.getMessageHistory(conversationId);
|
||||||
|
|
||||||
// For now: echo-stream mode. The actual LLM call will be wired when a provider
|
// ECHO-STREAM PLACEHOLDER (Phase 22):
|
||||||
// is configured. This streams tokens from the last user message content one word
|
// Streams the user's last message back word-by-word to fully exercise the SSE
|
||||||
// at a time as a functional placeholder that fully exercises the SSE pipeline.
|
// pipeline. Phase 23 replaces this block with:
|
||||||
// Phase 23+ will replace this with real LLM calls via the agent's adapterConfig.
|
// const adapter = resolveAdapter(agentId);
|
||||||
|
// for await (const token of adapter.stream(history)) { ... }
|
||||||
const lastUserMsg = history.filter(m => m.role === "user").at(-1);
|
const lastUserMsg = history.filter(m => m.role === "user").at(-1);
|
||||||
const echoContent = lastUserMsg
|
const echoContent = lastUserMsg
|
||||||
? `Echo from agent: ${lastUserMsg.content}`
|
? `Echo from agent: ${lastUserMsg.content}`
|
||||||
|
|
@ -325,9 +339,9 @@ res.write(":ok\n\n");
|
||||||
|
|
||||||
Import `editMessageSchema` and `streamMessageSchema` from `@paperclipai/shared` at the top of the routes file (alongside existing imports).
|
Import `editMessageSchema` and `streamMessageSchema` from `@paperclipai/shared` at the top of the routes file (alongside existing imports).
|
||||||
|
|
||||||
3. **Stream tests** — Create `server/src/__tests__/chat-stream-routes.test.ts`:
|
3. **Stream tests** -- Create `server/src/__tests__/chat-stream-routes.test.ts`:
|
||||||
- Use the same test DB setup pattern as `chat-routes.test.ts` (read that file for the pattern).
|
- Use the same test DB setup pattern as `chat-routes.test.ts` (read that file for the pattern).
|
||||||
- Test: `GET /conversations/:id/stream?triggerMessageId=X` — create conversation, add user message, open stream, collect all SSE data events, verify:
|
- Test: `GET /conversations/:id/stream?triggerMessageId=X` -- create conversation, add user message, open stream, collect all SSE data events, verify:
|
||||||
- Response status is 200
|
- Response status is 200
|
||||||
- Content-Type header contains "text/event-stream"
|
- Content-Type header contains "text/event-stream"
|
||||||
- X-Accel-Buffering header is "no"
|
- X-Accel-Buffering header is "no"
|
||||||
|
|
@ -352,26 +366,27 @@ res.write(":ok\n\n");
|
||||||
- grep -q "flushHeaders" server/src/routes/chat.ts returns 0
|
- grep -q "flushHeaders" server/src/routes/chat.ts returns 0
|
||||||
- grep -q 'type: "done"' server/src/routes/chat.ts returns 0
|
- grep -q 'type: "done"' server/src/routes/chat.ts returns 0
|
||||||
- grep -q 'type: "token"' server/src/routes/chat.ts returns 0
|
- grep -q 'type: "token"' server/src/routes/chat.ts returns 0
|
||||||
|
- grep -q "ECHO-STREAM PLACEHOLDER" server/src/routes/chat.ts returns 0
|
||||||
- test -f server/src/__tests__/chat-stream-routes.test.ts
|
- test -f server/src/__tests__/chat-stream-routes.test.ts
|
||||||
- pnpm --filter @paperclipai/server test run -- chat-stream exits 0
|
- pnpm --filter @paperclipai/server test run -- chat-stream exits 0
|
||||||
- pnpm --filter @paperclipai/server test run exits 0 (all server tests green)
|
- pnpm --filter @paperclipai/server test run exits 0 (all server tests green)
|
||||||
</acceptance_criteria>
|
</acceptance_criteria>
|
||||||
<done>SSE stream endpoint returns text/event-stream with token+done events, edit message route works, abort detection stops streaming, all server tests pass</done>
|
<done>SSE echo-stream endpoint returns text/event-stream with token+done events (placeholder for Phase 23 LLM integration), edit message route works, abort detection stops streaming, all server tests pass</done>
|
||||||
</task>
|
</task>
|
||||||
|
|
||||||
</tasks>
|
</tasks>
|
||||||
|
|
||||||
<verification>
|
<verification>
|
||||||
- `pnpm --filter @paperclipai/server test run` — all server tests pass
|
- `pnpm --filter @paperclipai/server test run` -- all server tests pass
|
||||||
- `pnpm db:generate` has been run and migration exists
|
- `pnpm db:generate` has been run and migration exists
|
||||||
- SSE endpoint tested with token + done events
|
- SSE endpoint tested with token + done events (echo-stream mode)
|
||||||
- Edit message route tested with editedContent persistence
|
- Edit message route tested with editedContent persistence
|
||||||
- PATCH conversation with agentId tested
|
- PATCH conversation with agentId tested
|
||||||
</verification>
|
</verification>
|
||||||
|
|
||||||
<success_criteria>
|
<success_criteria>
|
||||||
1. New migration SQL exists and applies the editedContent + editedAt columns
|
1. New migration SQL exists and applies the editedContent + editedAt columns
|
||||||
2. GET /conversations/:id/stream returns text/event-stream with token events then done event
|
2. GET /conversations/:id/stream returns text/event-stream with token events then done event (echo-stream placeholder -- Phase 23 replaces with real LLM)
|
||||||
3. PUT /conversations/:id/messages/:messageId updates editedContent and editedAt
|
3. PUT /conversations/:id/messages/:messageId updates editedContent and editedAt
|
||||||
4. PATCH /conversations/:id with { agentId } persists the agent selection
|
4. PATCH /conversations/:id with { agentId } persists the agent selection
|
||||||
5. All server tests pass (both chat-routes and chat-stream-routes)
|
5. All server tests pass (both chat-routes and chat-stream-routes)
|
||||||
|
|
|
||||||
|
|
@ -36,6 +36,8 @@ must_haves:
|
||||||
- path: "ui/src/components/AgentSelector.tsx"
|
- path: "ui/src/components/AgentSelector.tsx"
|
||||||
provides: "Dropdown to select active agent per conversation"
|
provides: "Dropdown to select active agent per conversation"
|
||||||
exports: ["AgentSelector"]
|
exports: ["AgentSelector"]
|
||||||
|
- path: "ui/src/components/ChatInput.slash-mention.test.tsx"
|
||||||
|
provides: "Integration tests for slash command and @mention parsing in ChatInput context"
|
||||||
key_links:
|
key_links:
|
||||||
- from: "ui/src/components/ChatAgentBadge.tsx"
|
- from: "ui/src/components/ChatAgentBadge.tsx"
|
||||||
to: "ui/src/lib/agent-colors.ts"
|
to: "ui/src/lib/agent-colors.ts"
|
||||||
|
|
@ -120,16 +122,19 @@ Shadcn components already installed (per UI-SPEC): Avatar, Badge, Select, Toolti
|
||||||
<tasks>
|
<tasks>
|
||||||
|
|
||||||
<task type="auto" tdd="true">
|
<task type="auto" tdd="true">
|
||||||
<name>Task 1: Agent color utility + parseMessageIntent function + tests</name>
|
<name>Task 1: Agent color utility + parseMessageIntent function + tests (including slash/mention integration stubs)</name>
|
||||||
<files>
|
<files>
|
||||||
ui/src/lib/agent-colors.ts,
|
ui/src/lib/agent-colors.ts,
|
||||||
ui/src/lib/parseMessageIntent.ts,
|
ui/src/lib/parseMessageIntent.ts,
|
||||||
ui/src/lib/parseMessageIntent.test.ts
|
ui/src/lib/parseMessageIntent.test.ts,
|
||||||
|
ui/src/components/ChatInput.slash-mention.test.tsx
|
||||||
</files>
|
</files>
|
||||||
<read_first>
|
<read_first>
|
||||||
ui/src/lib/agent-icons.ts,
|
ui/src/lib/agent-icons.ts,
|
||||||
ui/src/lib/utils.ts,
|
ui/src/lib/utils.ts,
|
||||||
ui/src/index.css (search for --chart-1 through --chart-5 definitions),
|
ui/src/index.css (search for --chart-1 through --chart-5 definitions),
|
||||||
|
ui/src/components/ChatInput.tsx,
|
||||||
|
ui/src/components/ChatInput.test.tsx,
|
||||||
.planning/phases/22-agent-streaming/22-UI-SPEC.md (Color section, agent role colors table),
|
.planning/phases/22-agent-streaming/22-UI-SPEC.md (Color section, agent role colors table),
|
||||||
.planning/phases/22-agent-streaming/22-RESEARCH.md (Pattern 4: slash command parsing, Pattern 6: agent colors)
|
.planning/phases/22-agent-streaming/22-RESEARCH.md (Pattern 4: slash command parsing, Pattern 6: agent colors)
|
||||||
</read_first>
|
</read_first>
|
||||||
|
|
@ -149,7 +154,7 @@ Shadcn components already installed (per UI-SPEC): Avatar, Badge, Select, Toolti
|
||||||
- Test: parseMessageIntent("@PM-agent Check this") returns { text: "Check this", targetName: "pm-agent" }
|
- Test: parseMessageIntent("@PM-agent Check this") returns { text: "Check this", targetName: "pm-agent" }
|
||||||
- Test: parseMessageIntent("/unknown-command Hello") returns { text: "/unknown-command Hello" } (no targetRole)
|
- Test: parseMessageIntent("/unknown-command Hello") returns { text: "/unknown-command Hello" } (no targetRole)
|
||||||
- Test: parseMessageIntent("Just a normal message") returns { text: "Just a normal message" }
|
- Test: parseMessageIntent("Just a normal message") returns { text: "Just a normal message" }
|
||||||
- Test: parseMessageIntent("/path/to/file.ts") returns { text: "/path/to/file.ts" } (no targetRole — not followed by space)
|
- Test: parseMessageIntent("/path/to/file.ts") returns { text: "/path/to/file.ts" } (no targetRole -- not followed by space)
|
||||||
</behavior>
|
</behavior>
|
||||||
<action>
|
<action>
|
||||||
1. **Create `ui/src/lib/agent-colors.ts`:**
|
1. **Create `ui/src/lib/agent-colors.ts`:**
|
||||||
|
|
@ -209,22 +214,32 @@ Shadcn components already installed (per UI-SPEC): Avatar, Badge, Select, Toolti
|
||||||
```
|
```
|
||||||
|
|
||||||
3. **Create `ui/src/lib/parseMessageIntent.test.ts`:** Write Vitest tests covering all the behaviors listed above. Use `describe("parseMessageIntent", () => { ... })` and `describe("agentRoleColorClass", () => { ... })` blocks. Import from the respective modules.
|
3. **Create `ui/src/lib/parseMessageIntent.test.ts`:** Write Vitest tests covering all the behaviors listed above. Use `describe("parseMessageIntent", () => { ... })` and `describe("agentRoleColorClass", () => { ... })` blocks. Import from the respective modules.
|
||||||
|
|
||||||
|
4. **Create `ui/src/components/ChatInput.slash-mention.test.tsx`:** Create test stubs for INPUT-05 (slash command parsing in ChatInput context) and INPUT-06 (@mention parsing in ChatInput context). These test the integration between ChatInput and parseMessageIntent:
|
||||||
|
- Use the same jsdom + createRoot + act pattern as `ChatInput.test.tsx`.
|
||||||
|
- Test stub (INPUT-05): "slash command prefix filters SLASH_COMMANDS and shows popover" -- import `SLASH_COMMANDS`, verify the exported constant has entries for /brainstorm, /ask-pm, /ask-engineer, /task, /search. (Full popover rendering tests will be added in Plan 03 when the popover is wired into ChatInput.)
|
||||||
|
- Test stub (INPUT-06): "@mention prefix resolves agent name" -- import `parseMessageIntent`, verify `parseMessageIntent("@test-agent hello").targetName` equals "test-agent". (Full popover rendering tests added in Plan 03.)
|
||||||
|
- Mark any tests that depend on Plan 03 UI changes with `it.todo(...)` so they are tracked but do not block.
|
||||||
</action>
|
</action>
|
||||||
<verify>
|
<verify>
|
||||||
<automated>pnpm --filter @paperclipai/ui test run -- --reporter=verbose parseMessageIntent</automated>
|
<automated>pnpm --filter @paperclipai/ui test run -- --reporter=verbose parseMessageIntent ChatInput.slash</automated>
|
||||||
</verify>
|
</verify>
|
||||||
<acceptance_criteria>
|
<acceptance_criteria>
|
||||||
- test -f ui/src/lib/agent-colors.ts
|
- test -f ui/src/lib/agent-colors.ts
|
||||||
- test -f ui/src/lib/parseMessageIntent.ts
|
- test -f ui/src/lib/parseMessageIntent.ts
|
||||||
- test -f ui/src/lib/parseMessageIntent.test.ts
|
- test -f ui/src/lib/parseMessageIntent.test.ts
|
||||||
|
- test -f ui/src/components/ChatInput.slash-mention.test.tsx
|
||||||
- grep -q "agentRoleColorClass" ui/src/lib/agent-colors.ts returns 0
|
- grep -q "agentRoleColorClass" ui/src/lib/agent-colors.ts returns 0
|
||||||
- grep -q "parseMessageIntent" ui/src/lib/parseMessageIntent.ts returns 0
|
- grep -q "parseMessageIntent" ui/src/lib/parseMessageIntent.ts returns 0
|
||||||
- grep -q "SLASH_COMMANDS" ui/src/lib/parseMessageIntent.ts returns 0
|
- grep -q "SLASH_COMMANDS" ui/src/lib/parseMessageIntent.ts returns 0
|
||||||
- grep -q "/brainstorm" ui/src/lib/parseMessageIntent.ts returns 0
|
- grep -q "/brainstorm" ui/src/lib/parseMessageIntent.ts returns 0
|
||||||
- grep -q "@mention" ui/src/lib/parseMessageIntent.ts OR grep -q "targetName" returns 0
|
- grep -q "@mention" ui/src/lib/parseMessageIntent.ts OR grep -q "targetName" returns 0
|
||||||
|
- grep -q "INPUT-05" ui/src/components/ChatInput.slash-mention.test.tsx returns 0
|
||||||
|
- grep -q "INPUT-06" ui/src/components/ChatInput.slash-mention.test.tsx returns 0
|
||||||
- pnpm --filter @paperclipai/ui test run -- parseMessageIntent exits 0
|
- pnpm --filter @paperclipai/ui test run -- parseMessageIntent exits 0
|
||||||
|
- pnpm --filter @paperclipai/ui test run -- ChatInput.slash exits 0
|
||||||
</acceptance_criteria>
|
</acceptance_criteria>
|
||||||
<done>Agent color mapping utility and message intent parsing with slash commands + @mentions both implemented and fully tested</done>
|
<done>Agent color mapping utility and message intent parsing with slash commands + @mentions both implemented and fully tested. ChatInput slash/mention integration test stubs created for INPUT-05 and INPUT-06.</done>
|
||||||
</task>
|
</task>
|
||||||
|
|
||||||
<task type="auto">
|
<task type="auto">
|
||||||
|
|
@ -253,14 +268,14 @@ Shadcn components already installed (per UI-SPEC): Avatar, Badge, Select, Toolti
|
||||||
Layout per UI-SPEC:
|
Layout per UI-SPEC:
|
||||||
- Container: `flex items-center gap-2 mb-1`
|
- Container: `flex items-center gap-2 mb-1`
|
||||||
- Avatar circle: `w-5 h-5 rounded-full flex items-center justify-center` + `agentRoleColorClass(agent.role)` background + `text-white`
|
- Avatar circle: `w-5 h-5 rounded-full flex items-center justify-center` + `agentRoleColorClass(agent.role)` background + `text-white`
|
||||||
- If agent has an `icon` value: use the `AgentIcon` component at 12px (check how `agent-icons.ts` maps icon strings to lucide components — read that file). If no `AgentIcon` component exists, render the lucide `Bot` icon at 12px.
|
- If agent has an `icon` value: use the `AgentIcon` component at 12px (check how `agent-icons.ts` maps icon strings to lucide components -- read that file). If no `AgentIcon` component exists, render the lucide `Bot` icon at 12px.
|
||||||
- If no icon: render first letter of `agent.name` at `text-[10px] font-semibold text-white`
|
- If no icon: render first letter of `agent.name` at `text-[10px] font-semibold text-white`
|
||||||
- Agent name: `<span className="text-[13px] text-muted-foreground truncate max-w-[120px]" aria-label={`Agent: ${agent.name}`}>`
|
- Agent name: `<span className="text-[13px] text-muted-foreground truncate max-w-[120px]" aria-label={`Agent: ${agent.name}`}>`
|
||||||
- Fallback (agent not found): `Bot` icon (12px) + "Agent" text, `bg-muted` background
|
- Fallback (agent not found): `Bot` icon (12px) + "Agent" text, `bg-muted` background
|
||||||
- Avatar element: `aria-hidden="true"` (decorative per accessibility contract)
|
- Avatar element: `aria-hidden="true"` (decorative per accessibility contract)
|
||||||
|
|
||||||
2. **Create `ui/src/components/ChatAgentBadge.test.tsx`:**
|
2. **Create `ui/src/components/ChatAgentBadge.test.tsx`:**
|
||||||
Use jsdom + createRoot + act pattern (same as `ChatInput.test.tsx` — read that file for the testing pattern). NOT `@testing-library/react`.
|
Use jsdom + createRoot + act pattern (same as `ChatInput.test.tsx` -- read that file for the testing pattern). NOT `@testing-library/react`.
|
||||||
|
|
||||||
Tests:
|
Tests:
|
||||||
- Renders agent name when agentId matches an agent in the array
|
- Renders agent name when agentId matches an agent in the array
|
||||||
|
|
@ -284,7 +299,7 @@ Shadcn components already installed (per UI-SPEC): Avatar, Badge, Select, Toolti
|
||||||
- If `isLoading`: render `<Skeleton className="h-8 w-28" />`
|
- If `isLoading`: render `<Skeleton className="h-8 w-28" />`
|
||||||
- On value change: call `onSelect(value)`
|
- On value change: call `onSelect(value)`
|
||||||
|
|
||||||
No test file needed for AgentSelector (it's a thin UI wrapper over shadcn Select with no logic — the color mapping is tested via agent-colors, and the integration will be verified in Plan 03's checkpoint).
|
No test file needed for AgentSelector (it's a thin UI wrapper over shadcn Select with no logic -- the color mapping is tested via agent-colors, and the integration will be verified in Plan 03's checkpoint).
|
||||||
</action>
|
</action>
|
||||||
<verify>
|
<verify>
|
||||||
<automated>pnpm --filter @paperclipai/ui test run -- --reporter=verbose ChatAgentBadge</automated>
|
<automated>pnpm --filter @paperclipai/ui test run -- --reporter=verbose ChatAgentBadge</automated>
|
||||||
|
|
@ -308,11 +323,12 @@ Shadcn components already installed (per UI-SPEC): Avatar, Badge, Select, Toolti
|
||||||
</tasks>
|
</tasks>
|
||||||
|
|
||||||
<verification>
|
<verification>
|
||||||
- `pnpm --filter @paperclipai/ui test run` — all UI tests pass
|
- `pnpm --filter @paperclipai/ui test run` -- all UI tests pass
|
||||||
- `pnpm --filter @paperclipai/ui build` — TypeScript compiles
|
- `pnpm --filter @paperclipai/ui build` -- TypeScript compiles
|
||||||
- Agent color utility tested for all 5 roles + fallback
|
- Agent color utility tested for all 5 roles + fallback
|
||||||
- parseMessageIntent tested for all 5 slash commands + @mention + plain text + edge cases
|
- parseMessageIntent tested for all 5 slash commands + @mention + plain text + edge cases
|
||||||
- ChatAgentBadge tested for render + fallback + accessibility
|
- ChatAgentBadge tested for render + fallback + accessibility
|
||||||
|
- ChatInput.slash-mention.test.tsx exists with INPUT-05 and INPUT-06 stubs
|
||||||
</verification>
|
</verification>
|
||||||
|
|
||||||
<success_criteria>
|
<success_criteria>
|
||||||
|
|
@ -320,7 +336,8 @@ Shadcn components already installed (per UI-SPEC): Avatar, Badge, Select, Toolti
|
||||||
2. parseMessageIntent correctly parses all 5 slash commands and @mention syntax
|
2. parseMessageIntent correctly parses all 5 slash commands and @mention syntax
|
||||||
3. ChatAgentBadge renders agent name + colored avatar, with fallback for unknown agents
|
3. ChatAgentBadge renders agent name + colored avatar, with fallback for unknown agents
|
||||||
4. AgentSelector provides a dropdown with tooltip and empty state
|
4. AgentSelector provides a dropdown with tooltip and empty state
|
||||||
5. All UI tests pass and build succeeds
|
5. ChatInput.slash-mention.test.tsx has test stubs for INPUT-05 and INPUT-06
|
||||||
|
6. All UI tests pass and build succeeds
|
||||||
</success_criteria>
|
</success_criteria>
|
||||||
|
|
||||||
<output>
|
<output>
|
||||||
|
|
|
||||||
|
|
@ -202,7 +202,7 @@ import { VList } from "virtua";
|
||||||
pnpm --filter @paperclipai/ui add virtua
|
pnpm --filter @paperclipai/ui add virtua
|
||||||
```
|
```
|
||||||
|
|
||||||
1. **Extend `ui/src/api/chat.ts`** — Add these methods to the `chatApi` object:
|
1. **Extend `ui/src/api/chat.ts`** -- Add these methods to the `chatApi` object:
|
||||||
```typescript
|
```typescript
|
||||||
editMessage: (conversationId: string, messageId: string, data: { content: string }) =>
|
editMessage: (conversationId: string, messageId: string, data: { content: string }) =>
|
||||||
api.put<ChatMessage>(`/api/conversations/${conversationId}/messages/${messageId}`, data),
|
api.put<ChatMessage>(`/api/conversations/${conversationId}/messages/${messageId}`, data),
|
||||||
|
|
@ -210,7 +210,7 @@ import { VList } from "virtua";
|
||||||
api.patch<ChatConversation>(`/api/conversations/${id}`, { agentId }),
|
api.patch<ChatConversation>(`/api/conversations/${id}`, { agentId }),
|
||||||
```
|
```
|
||||||
|
|
||||||
2. **Extend `ui/src/hooks/useChatMessages.ts`** — Add `useStreamMessage` hook:
|
2. **Extend `ui/src/hooks/useChatMessages.ts`** -- Add `useStreamMessage` hook:
|
||||||
```typescript
|
```typescript
|
||||||
export function useStreamMessage(conversationId: string | null) {
|
export function useStreamMessage(conversationId: string | null) {
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
|
|
@ -265,7 +265,7 @@ import { VList } from "virtua";
|
||||||
esRef.current = null;
|
esRef.current = null;
|
||||||
setStreaming(false);
|
setStreaming(false);
|
||||||
setPartialContent("");
|
setPartialContent("");
|
||||||
// Toast would go here — for now log
|
// Toast would go here -- for now log
|
||||||
console.error("Stream error:", parsed.message);
|
console.error("Stream error:", parsed.message);
|
||||||
}
|
}
|
||||||
} catch {
|
} catch {
|
||||||
|
|
@ -283,7 +283,7 @@ import { VList } from "virtua";
|
||||||
|
|
||||||
const retry = useCallback(async (agentId?: string | null) => {
|
const retry = useCallback(async (agentId?: string | null) => {
|
||||||
if (!conversationId || streaming) return;
|
if (!conversationId || streaming) return;
|
||||||
// Retry: open stream without posting a new message — server re-generates from last user message
|
// Retry: open stream without posting a new message -- server re-generates from last user message
|
||||||
setStreaming(true);
|
setStreaming(true);
|
||||||
setPartialContent("");
|
setPartialContent("");
|
||||||
|
|
||||||
|
|
@ -342,7 +342,7 @@ import { VList } from "virtua";
|
||||||
|
|
||||||
Add required imports at top: `useState, useCallback, useRef, useEffect` from react.
|
Add required imports at top: `useState, useCallback, useRef, useEffect` from react.
|
||||||
|
|
||||||
3. **Extend `ui/src/hooks/useChatConversations.ts`** — Add `useUpdateConversationAgent` hook:
|
3. **Extend `ui/src/hooks/useChatConversations.ts`** -- Add `useUpdateConversationAgent` hook:
|
||||||
```typescript
|
```typescript
|
||||||
export function useUpdateConversationAgent() {
|
export function useUpdateConversationAgent() {
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
|
|
@ -359,7 +359,7 @@ import { VList } from "virtua";
|
||||||
Add import for `chatApi` (should already be imported; if not, add `import { chatApi } from "../api/chat";`).
|
Add import for `chatApi` (should already be imported; if not, add `import { chatApi } from "../api/chat";`).
|
||||||
</action>
|
</action>
|
||||||
<verify>
|
<verify>
|
||||||
<automated>pnpm --filter @paperclipai/ui build</automated>
|
<automated>pnpm --filter @paperclipai/ui build && pnpm --filter @paperclipai/ui test run</automated>
|
||||||
</verify>
|
</verify>
|
||||||
<acceptance_criteria>
|
<acceptance_criteria>
|
||||||
- grep -q "virtua" ui/package.json returns 0
|
- grep -q "virtua" ui/package.json returns 0
|
||||||
|
|
@ -372,8 +372,9 @@ import { VList } from "virtua";
|
||||||
- grep -q "partialContent" ui/src/hooks/useChatMessages.ts returns 0
|
- grep -q "partialContent" ui/src/hooks/useChatMessages.ts returns 0
|
||||||
- grep -q "streaming" ui/src/hooks/useChatMessages.ts returns 0
|
- grep -q "streaming" ui/src/hooks/useChatMessages.ts returns 0
|
||||||
- pnpm --filter @paperclipai/ui build exits 0
|
- pnpm --filter @paperclipai/ui build exits 0
|
||||||
|
- pnpm --filter @paperclipai/ui test run exits 0
|
||||||
</acceptance_criteria>
|
</acceptance_criteria>
|
||||||
<done>virtua installed, API client extended with editMessage + updateConversationAgent, useStreamMessage hook with EventSource streaming + stop + retry, useEditMessage mutation, useUpdateConversationAgent mutation, build passes</done>
|
<done>virtua installed, API client extended with editMessage + updateConversationAgent, useStreamMessage hook with EventSource streaming + stop + retry, useEditMessage mutation, useUpdateConversationAgent mutation, build and tests pass. Note: useStreamMessage is tested via the full integration in Plan 04's visual checkpoint rather than unit tests, since EventSource requires complex browser mocking -- the hook's logic is straightforward state management over a well-tested SSE endpoint.</done>
|
||||||
</task>
|
</task>
|
||||||
|
|
||||||
<task type="auto">
|
<task type="auto">
|
||||||
|
|
@ -397,6 +398,11 @@ import { VList } from "virtua";
|
||||||
.planning/phases/22-agent-streaming/22-UI-SPEC.md (full Interaction Contract + Component Inventory)
|
.planning/phases/22-agent-streaming/22-UI-SPEC.md (full Interaction Contract + Component Inventory)
|
||||||
</read_first>
|
</read_first>
|
||||||
<action>
|
<action>
|
||||||
|
NOTE: This task touches 3 tightly-coupled components that share streaming state. They are kept
|
||||||
|
in one task because splitting would create artificial seams -- ChatPanel owns the state that
|
||||||
|
ChatMessageList and ChatInput consume. Implement in order: A (ChatMessageList), B (ChatInput),
|
||||||
|
C (ChatPanel wiring).
|
||||||
|
|
||||||
**A. Rewrite `ChatMessageList.tsx`** to use virtua VList with agent badges and action buttons:
|
**A. Rewrite `ChatMessageList.tsx`** to use virtua VList with agent badges and action buttons:
|
||||||
|
|
||||||
Replace the entire component. New props interface:
|
Replace the entire component. New props interface:
|
||||||
|
|
@ -449,7 +455,7 @@ import { VList } from "virtua";
|
||||||
```
|
```
|
||||||
3. Track `isAtBottom` state: use VList's `onScroll` callback. Virtua's VList provides `onScroll` with the scroll offset. Calculate: `isAtBottom = (event.scrollOffset + event.viewportSize >= event.scrollSize - 80)`. Initialize `isAtBottom` to `true`.
|
3. Track `isAtBottom` state: use VList's `onScroll` callback. Virtua's VList provides `onScroll` with the scroll offset. Calculate: `isAtBottom = (event.scrollOffset + event.viewportSize >= event.scrollSize - 80)`. Initialize `isAtBottom` to `true`.
|
||||||
4. Auto-scroll during streaming: when `streaming` is true and `isAtBottom`, after each partialContent change, call `listRef.current?.scrollToIndex(allMessages.length, { smooth: false })` via a useEffect.
|
4. Auto-scroll during streaming: when `streaming` is true and `isAtBottom`, after each partialContent change, call `listRef.current?.scrollToIndex(allMessages.length, { smooth: false })` via a useEffect.
|
||||||
5. Keep `role="log"` and `aria-live="polite"` on an outer wrapper div (not the VList itself — VList is the scroll container).
|
5. Keep `role="log"` and `aria-live="polite"` on an outer wrapper div (not the VList itself -- VList is the scroll container).
|
||||||
|
|
||||||
**MessageItem** (inline component or extracted):
|
**MessageItem** (inline component or extracted):
|
||||||
```tsx
|
```tsx
|
||||||
|
|
@ -508,7 +514,7 @@ import { VList } from "virtua";
|
||||||
{message.editedAt && " (edited)"}
|
{message.editedAt && " (edited)"}
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
{/* Action buttons — visible on hover, hidden during streaming */}
|
{/* Action buttons -- visible on hover, hidden during streaming */}
|
||||||
{!streaming && !editing && (
|
{!streaming && !editing && (
|
||||||
<div className="flex justify-end gap-1 mt-1 opacity-0 group-hover:opacity-100 transition-opacity duration-150">
|
<div className="flex justify-end gap-1 mt-1 opacity-0 group-hover:opacity-100 transition-opacity duration-150">
|
||||||
{message.role === "user" && (
|
{message.role === "user" && (
|
||||||
|
|
@ -544,7 +550,7 @@ import { VList } from "virtua";
|
||||||
|
|
||||||
Imports needed: `VList` from `virtua`, `ChatAgentBadge` from `./ChatAgentBadge`, `ChatMarkdownMessage` from `./ChatMarkdownMessage`, `Button` from `@/components/ui/button`, `Pencil, RotateCcw, ChevronDown` from `lucide-react`, `Agent` from `@paperclipai/shared`, `useState, useRef, useEffect, useCallback` from `react`, `cn` from `../lib/utils`.
|
Imports needed: `VList` from `virtua`, `ChatAgentBadge` from `./ChatAgentBadge`, `ChatMarkdownMessage` from `./ChatMarkdownMessage`, `Button` from `@/components/ui/button`, `Pencil, RotateCcw, ChevronDown` from `lucide-react`, `Agent` from `@paperclipai/shared`, `useState, useRef, useEffect, useCallback` from `react`, `cn` from `../lib/utils`.
|
||||||
|
|
||||||
**B. Update `ChatInput.tsx`** — Add Stop button, slash command popover, @mention popover:
|
**B. Update `ChatInput.tsx`** -- Add Stop button, slash command popover, @mention popover:
|
||||||
|
|
||||||
New props interface:
|
New props interface:
|
||||||
```typescript
|
```typescript
|
||||||
|
|
@ -603,9 +609,9 @@ import { VList } from "virtua";
|
||||||
- Render same `<Popover>` + `<Command>` pattern
|
- Render same `<Popover>` + `<Command>` pattern
|
||||||
- On item select: replace input with `@{agentName} `, close popover
|
- On item select: replace input with `@{agentName} `, close popover
|
||||||
|
|
||||||
The popover trigger is the textarea container itself (invisible trigger — use `<PopoverAnchor>` on the textarea wrapper div).
|
The popover trigger is the textarea container itself (invisible trigger -- use `<PopoverAnchor>` on the textarea wrapper div).
|
||||||
|
|
||||||
**C. Update `ChatPanel.tsx`** — Wire everything together:
|
**C. Update `ChatPanel.tsx`** -- Wire everything together:
|
||||||
|
|
||||||
1. Import `AgentSelector` from `./AgentSelector`.
|
1. Import `AgentSelector` from `./AgentSelector`.
|
||||||
2. Import `useStreamMessage, useEditMessage` from `../hooks/useChatMessages`.
|
2. Import `useStreamMessage, useEditMessage` from `../hooks/useChatMessages`.
|
||||||
|
|
@ -650,7 +656,7 @@ import { VList } from "virtua";
|
||||||
try {
|
try {
|
||||||
const conversation = await createConversation.mutateAsync(undefined);
|
const conversation = await createConversation.mutateAsync(undefined);
|
||||||
setActiveConversationId(conversation.id);
|
setActiveConversationId(conversation.id);
|
||||||
// Can't stream yet — conversation just created, need to wait for state update
|
// Can't stream yet -- conversation just created, need to wait for state update
|
||||||
// Queue the send for after state settles
|
// Queue the send for after state settles
|
||||||
setTimeout(() => stream.send(content, resolveAgentId(intent, agents, conversation.agentId)), 50);
|
setTimeout(() => stream.send(content, resolveAgentId(intent, agents, conversation.agentId)), 50);
|
||||||
} catch { /* ignore */ }
|
} catch { /* ignore */ }
|
||||||
|
|
@ -704,7 +710,7 @@ import { VList } from "virtua";
|
||||||
}, [editMessage, stream, activeConversation]);
|
}, [editMessage, stream, activeConversation]);
|
||||||
```
|
```
|
||||||
|
|
||||||
16. Add `AgentSelector` to the panel header. Modify the inner layout — add a header bar above the message area:
|
16. Add `AgentSelector` to the panel header. Modify the inner layout -- add a header bar above the message area:
|
||||||
```tsx
|
```tsx
|
||||||
{/* Message area */}
|
{/* Message area */}
|
||||||
<div className="flex flex-1 flex-col min-w-0 overflow-hidden">
|
<div className="flex flex-1 flex-col min-w-0 overflow-hidden">
|
||||||
|
|
@ -778,9 +784,9 @@ import { VList } from "virtua";
|
||||||
</tasks>
|
</tasks>
|
||||||
|
|
||||||
<verification>
|
<verification>
|
||||||
- `pnpm --filter @paperclipai/ui build` — TypeScript compiles
|
- `pnpm --filter @paperclipai/ui build` -- TypeScript compiles
|
||||||
- `pnpm --filter @paperclipai/ui test run` — all UI tests pass
|
- `pnpm --filter @paperclipai/ui test run` -- all UI tests pass
|
||||||
- `pnpm test run` — full suite green
|
- `pnpm test run` -- full suite green
|
||||||
- ChatMessageList uses VList from virtua
|
- ChatMessageList uses VList from virtua
|
||||||
- ChatInput shows Stop button during streaming
|
- ChatInput shows Stop button during streaming
|
||||||
- ChatPanel has AgentSelector in header
|
- ChatPanel has AgentSelector in header
|
||||||
|
|
|
||||||
|
|
@ -10,9 +10,14 @@ requirements: [CHAT-01, CHAT-08, CHAT-10, CHAT-11, CHAT-12, INPUT-05, INPUT-06,
|
||||||
|
|
||||||
must_haves:
|
must_haves:
|
||||||
truths:
|
truths:
|
||||||
- "Full streaming chat flow works end-to-end"
|
- "pnpm test run exits 0 with all chat-routes, chat-stream-routes, parseMessageIntent, ChatAgentBadge, and ChatInput.slash-mention tests passing"
|
||||||
- "All three themes render agent colors correctly"
|
- "pnpm --filter @paperclipai/ui build and pnpm --filter @paperclipai/server build both exit 0"
|
||||||
- "All six success criteria from ROADMAP are met"
|
- "User sends a message and sees echo-stream tokens appear word-by-word in a streaming assistant bubble (CHAT-01 transport-level, echo-stream placeholder)"
|
||||||
|
- "Stop button (red square) appears during streaming and cancels the stream on click"
|
||||||
|
- "Agent selector dropdown in chat header shows agents with colored avatars and persists selection across page reload"
|
||||||
|
- "Agent badge with colored circle and name appears above each assistant message"
|
||||||
|
- "Slash command popover appears when typing / prefix, @mention popover appears when typing @ prefix"
|
||||||
|
- "Agent colors are visually distinguishable across all three themes (Catppuccin Mocha, Tokyo Night, Catppuccin Latte)"
|
||||||
artifacts: []
|
artifacts: []
|
||||||
key_links: []
|
key_links: []
|
||||||
---
|
---
|
||||||
|
|
@ -41,12 +46,15 @@ Output: Verified, working Phase 22.
|
||||||
|
|
||||||
<task type="auto">
|
<task type="auto">
|
||||||
<name>Task 1: Full test suite verification and build check</name>
|
<name>Task 1: Full test suite verification and build check</name>
|
||||||
<files></files>
|
<files>
|
||||||
|
(run-only verification task -- no source changes expected; reads test files for diagnostics if failures occur)
|
||||||
|
</files>
|
||||||
<read_first>
|
<read_first>
|
||||||
server/src/__tests__/chat-stream-routes.test.ts,
|
server/src/__tests__/chat-stream-routes.test.ts,
|
||||||
server/src/__tests__/chat-routes.test.ts,
|
server/src/__tests__/chat-routes.test.ts,
|
||||||
ui/src/lib/parseMessageIntent.test.ts,
|
ui/src/lib/parseMessageIntent.test.ts,
|
||||||
ui/src/components/ChatAgentBadge.test.tsx
|
ui/src/components/ChatAgentBadge.test.tsx,
|
||||||
|
ui/src/components/ChatInput.slash-mention.test.tsx
|
||||||
</read_first>
|
</read_first>
|
||||||
<action>
|
<action>
|
||||||
Run the full test suite and verify all tests pass:
|
Run the full test suite and verify all tests pass:
|
||||||
|
|
@ -70,42 +78,34 @@ Output: Verified, working Phase 22.
|
||||||
<verify>
|
<verify>
|
||||||
<automated>pnpm test run && pnpm --filter @paperclipai/ui build && pnpm --filter @paperclipai/server build</automated>
|
<automated>pnpm test run && pnpm --filter @paperclipai/ui build && pnpm --filter @paperclipai/server build</automated>
|
||||||
</verify>
|
</verify>
|
||||||
<acceptance_criteria>
|
|
||||||
- pnpm test run exits 0
|
|
||||||
- pnpm --filter @paperclipai/ui build exits 0
|
|
||||||
- pnpm --filter @paperclipai/server build exits 0
|
|
||||||
</acceptance_criteria>
|
|
||||||
<done>All tests pass and both UI and server build cleanly</done>
|
<done>All tests pass and both UI and server build cleanly</done>
|
||||||
</task>
|
</task>
|
||||||
|
|
||||||
<task type="checkpoint:human-verify" gate="blocking">
|
<task type="checkpoint:human-verify" gate="blocking">
|
||||||
<name>Task 2: Visual and functional verification of streaming chat</name>
|
<name>Task 2: Visual and functional verification of streaming chat</name>
|
||||||
<files></files>
|
<files>
|
||||||
|
(checkpoint -- no files modified; visual/functional verification only)
|
||||||
|
</files>
|
||||||
<action>
|
<action>
|
||||||
Present the verification checklist to the user. The user will manually test the streaming chat experience across all features built in Phase 22:
|
Present the verification checklist to the user. Complete Phase 22 agent streaming has been built:
|
||||||
|
SSE echo-stream endpoint, virtualized message list with agent badges, edit/retry actions,
|
||||||
|
Stop button, AgentSelector, slash command and @mention popovers.
|
||||||
|
|
||||||
|
The user will manually test:
|
||||||
1. Start the dev server: `pnpm dev`
|
1. Start the dev server: `pnpm dev`
|
||||||
2. Open the chat panel (MessageSquare icon in sidebar)
|
2. Open the chat panel (MessageSquare icon in sidebar)
|
||||||
3. Create a new conversation and send a message — verify tokens stream in word-by-word
|
3. Create a new conversation and send a message -- verify tokens stream in word-by-word (echo-stream: you will see your own message echoed back, this is the Phase 22 placeholder; real LLM responses come in Phase 23)
|
||||||
4. While streaming: verify the Stop button (red square) appears; click it to cancel
|
4. While streaming: verify the Stop button (red square) appears; click it to cancel
|
||||||
5. Hover over an assistant message — verify Retry button (rotate icon) appears; click it
|
5. Hover over an assistant message -- verify Retry button (rotate icon) appears; click it
|
||||||
6. Hover over a user message — verify Edit button (pencil icon) appears; click to enter edit mode, modify text, click Regenerate
|
6. Hover over a user message -- verify Edit button (pencil icon) appears; click to enter edit mode, modify text, click Regenerate
|
||||||
7. Open the Agent Selector dropdown in the header — verify agents appear with colored avatars
|
7. Open the Agent Selector dropdown in the header -- verify agents appear with colored avatars
|
||||||
8. Select a different agent — verify it persists (reload page, re-open conversation)
|
8. Select a different agent -- verify it persists (reload page, re-open conversation)
|
||||||
9. Type `/ask-pm ` — verify slash command popover appears with matching commands
|
9. Type `/ask-pm ` -- verify slash command popover appears with matching commands
|
||||||
10. Type `@` followed by an agent name — verify mention popover appears
|
10. Type `@` followed by an agent name -- verify mention popover appears
|
||||||
11. Switch between all three themes (Catppuccin Mocha, Tokyo Night, Catppuccin Latte) — verify agent badge colors are distinguishable
|
11. Switch between all three themes (Catppuccin Mocha, Tokyo Night, Catppuccin Latte) -- verify agent badge colors are distinguishable
|
||||||
12. (Optional) If you have a conversation with many messages, scroll rapidly to check smoothness
|
12. (Optional) If you have a conversation with many messages, scroll rapidly to check smoothness
|
||||||
</action>
|
</action>
|
||||||
<verify>User types "approved" or describes issues to fix</verify>
|
<verify>User types "approved" or describes issues to fix</verify>
|
||||||
<acceptance_criteria>
|
|
||||||
- User confirms streaming tokens appear as they are generated
|
|
||||||
- User confirms Stop button cancels in-progress stream
|
|
||||||
- User confirms agent badge shows on assistant messages with colored avatar
|
|
||||||
- User confirms agent selector changes the conversation's agent
|
|
||||||
- User confirms slash command and @mention popovers appear
|
|
||||||
- User confirms agent colors are distinguishable across all three themes
|
|
||||||
</acceptance_criteria>
|
|
||||||
<done>User has approved the complete Phase 22 streaming chat experience</done>
|
<done>User has approved the complete Phase 22 streaming chat experience</done>
|
||||||
</task>
|
</task>
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue