From 9ed6dd16b3c9e74c335848effe21caefbe28f385 Mon Sep 17 00:00:00 2001 From: Nexus Dev Date: Wed, 1 Apr 2026 21:37:20 +0000 Subject: [PATCH] =?UTF-8?q?docs(23-brainstormer-flow):=20create=20phase=20?= =?UTF-8?q?plan=20=E2=80=94=204=20plans=20across=203=20waves?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Plan 00 (Wave 0): DB migration for message_type, shared types/validators, test stubs Plan 01 (Wave 1): Server — addSystemMessage, handoff route, status-update route Plan 02 (Wave 1): UI — ChatSpecCard, ChatHandoffIndicator, ChatTaskCreatedBadge, ChatStatusUpdateBadge, useBrainstormerDefault Plan 03 (Wave 2): Wiring — ChatMessage dispatch, ChatMessageList propagation, ChatPanel brainstormer default, chatApi handoff Co-Authored-By: Claude Opus 4.6 (1M context) --- .planning/ROADMAP.md | 44 +- .../phases/23-brainstormer-flow/23-00-PLAN.md | 313 +++++++++++++++ .../phases/23-brainstormer-flow/23-01-PLAN.md | 285 +++++++++++++ .../phases/23-brainstormer-flow/23-02-PLAN.md | 370 +++++++++++++++++ .../phases/23-brainstormer-flow/23-03-PLAN.md | 377 ++++++++++++++++++ 5 files changed, 1354 insertions(+), 35 deletions(-) create mode 100644 .planning/phases/23-brainstormer-flow/23-00-PLAN.md create mode 100644 .planning/phases/23-brainstormer-flow/23-01-PLAN.md create mode 100644 .planning/phases/23-brainstormer-flow/23-02-PLAN.md create mode 100644 .planning/phases/23-brainstormer-flow/23-03-PLAN.md diff --git a/.planning/ROADMAP.md b/.planning/ROADMAP.md index aea175da..ce4c689f 100644 --- a/.planning/ROADMAP.md +++ b/.planning/ROADMAP.md @@ -76,15 +76,13 @@ Plans: 3. When the user clicks "Send to PM," a handoff indicator appears in the chat showing "Brainstormer → PM" with the spec content 4. The PM agent creates one or more Nexus issues from the spec; the user can see task IDs referenced in the PM's reply 5. When an Engineer or Generalist completes a task, a status update message appears in the relevant chat conversation -**Plans:** 6 plans +**Plans:** 4 plans Plans: -- [x] 22-00-PLAN.md — Wave 0: DB migration, shared types, install virtualizer, agent-role-colors, CSS, test stubs -- [x] 22-01-PLAN.md — SSE streaming endpoint + useStreamingChat hook -- [x] 22-02-PLAN.md — Agent identity bar, streaming cursor, agent selector -- [x] 22-03-PLAN.md — Edit/retry/stop message action controls -- [x] 22-04-PLAN.md — Slash commands and @mention popovers -- [ ] 22-05-PLAN.md — Virtualized message list + full ChatPanel integration +- [ ] 23-00-PLAN.md — DB migration (message_type column), shared types/validators, Wave 0 test stubs +- [ ] 23-01-PLAN.md — Server: addSystemMessage helper, handoff route, status-update route +- [ ] 23-02-PLAN.md — UI: ChatSpecCard, ChatHandoffIndicator, ChatTaskCreatedBadge, ChatStatusUpdateBadge, useBrainstormerDefault +- [ ] 23-03-PLAN.md — Wiring: ChatMessage dispatch, ChatMessageList propagation, ChatPanel brainstormer default, chatApi handoff **UI hint**: yes ### Phase 24: Search, History & Branching @@ -96,15 +94,7 @@ Plans: 2. User can bookmark any message and later filter or navigate to bookmarked messages 3. Editing a message that already has a response creates a new branch; both the original and the new branch are preserved and the user can switch between them 4. User can export any conversation as a Markdown file or as a JSON file containing all messages and metadata -**Plans:** 6 plans - -Plans: -- [x] 22-00-PLAN.md — Wave 0: DB migration, shared types, install virtualizer, agent-role-colors, CSS, test stubs -- [x] 22-01-PLAN.md — SSE streaming endpoint + useStreamingChat hook -- [x] 22-02-PLAN.md — Agent identity bar, streaming cursor, agent selector -- [x] 22-03-PLAN.md — Edit/retry/stop message action controls -- [ ] 22-04-PLAN.md — Slash commands and @mention popovers -- [ ] 22-05-PLAN.md — Virtualized message list + full ChatPanel integration +**Plans:** [To be planned] **UI hint**: yes ### Phase 25: File System @@ -119,15 +109,7 @@ Plans: 5. When an agent generates a placeholder asset, `PLACEHOLDERS.md` is updated in the project directory; when the placeholder is replaced, the DB records the replacement chain and the manifest reflects the change 6. A file uploaded in a conversation linked to a project lives in `files/projects//`; a file from an unlinked conversation lives in `files/chat//`; the user can promote a chat file to project scope 7. Voice input is available when local AI is enabled: user can hold the record button, speak, see a transcription preview, and confirm to send -**Plans:** 6 plans - -Plans: -- [ ] 22-00-PLAN.md — Wave 0: DB migration, shared types, install virtualizer, agent-role-colors, CSS, test stubs -- [ ] 22-01-PLAN.md — SSE streaming endpoint + useStreamingChat hook -- [ ] 22-02-PLAN.md — Agent identity bar, streaming cursor, agent selector -- [ ] 22-03-PLAN.md — Edit/retry/stop message action controls -- [ ] 22-04-PLAN.md — Slash commands and @mention popovers -- [ ] 22-05-PLAN.md — Virtualized message list + full ChatPanel integration +**Plans:** [To be planned] **UI hint**: yes ### Phase 26: PWA & Performance @@ -141,15 +123,7 @@ Plans: 4. On a phone, the input bar is sticky at the bottom of the screen, touch targets are large enough to tap without errors, and the layout resizes correctly when the software keyboard appears 5. Pulling down on the conversation list on mobile triggers a refresh; push notifications arrive for agent mentions, task completions, and handoff requests where the platform supports them 6. The initial page load on broadband completes in under 2 seconds and on a 3G connection in under 5 seconds; PWA cached load completes in under 1 second -**Plans:** 6 plans - -Plans: -- [ ] 22-00-PLAN.md — Wave 0: DB migration, shared types, install virtualizer, agent-role-colors, CSS, test stubs -- [ ] 22-01-PLAN.md — SSE streaming endpoint + useStreamingChat hook -- [ ] 22-02-PLAN.md — Agent identity bar, streaming cursor, agent selector -- [ ] 22-03-PLAN.md — Edit/retry/stop message action controls -- [ ] 22-04-PLAN.md — Slash commands and @mention popovers -- [ ] 22-05-PLAN.md — Virtualized message list + full ChatPanel integration +**Plans:** [To be planned] **UI hint**: yes --- @@ -232,7 +206,7 @@ All 65 v1 requirements are mapped to exactly one phase. No orphans. |-------|-----------|----------------|--------|-----------| | 21. Chat Foundation | v1.3 | 7/7 | Complete | 2026-04-01 | | 22. Agent Streaming | v1.3 | 6/6 | Complete | 2026-04-01 | -| 23. Brainstormer Flow | v1.3 | 0/? | Not started | - | +| 23. Brainstormer Flow | v1.3 | 0/4 | Planned | - | | 24. Search, History & Branching | v1.3 | 0/? | Not started | - | | 25. File System | v1.3 | 0/? | Not started | - | | 26. PWA & Performance | v1.3 | 0/? | Not started | - | diff --git a/.planning/phases/23-brainstormer-flow/23-00-PLAN.md b/.planning/phases/23-brainstormer-flow/23-00-PLAN.md new file mode 100644 index 00000000..9755d5a0 --- /dev/null +++ b/.planning/phases/23-brainstormer-flow/23-00-PLAN.md @@ -0,0 +1,313 @@ +--- +phase: 23-brainstormer-flow +plan: 00 +type: execute +wave: 0 +depends_on: [] +files_modified: + - packages/db/src/schema/chat_messages.ts + - packages/db/src/migrations/0049_add_message_type.sql + - packages/db/src/migrations/meta/_journal.json + - packages/shared/src/types/chat.ts + - packages/shared/src/validators/chat.ts + - packages/shared/src/index.ts + - ui/src/components/ChatSpecCard.test.tsx + - ui/src/components/ChatHandoffIndicator.test.tsx + - ui/src/components/ChatTaskCreatedBadge.test.tsx + - ui/src/components/ChatStatusUpdateBadge.test.tsx + - ui/src/hooks/useBrainstormerDefault.test.ts +autonomous: true +requirements: + - AGENT-01 + - AGENT-02 + - AGENT-03 + - AGENT-05 + - AGENT-06 + - AGENT-07 + - CHAT-09 + +must_haves: + truths: + - "chat_messages table has a message_type text column" + - "ChatMessage shared type includes messageType field" + - "createMessageSchema accepts optional messageType" + - "handoffSchema validates spec content and targetRole" + - "Test stubs exist for all new Phase 23 components and hooks" + artifacts: + - path: "packages/db/src/schema/chat_messages.ts" + provides: "messageType column definition" + contains: "messageType" + - path: "packages/db/src/migrations/0049_add_message_type.sql" + provides: "SQL migration for message_type column" + contains: "ADD COLUMN" + - path: "packages/shared/src/types/chat.ts" + provides: "ChatMessage.messageType field" + contains: "messageType" + - path: "packages/shared/src/validators/chat.ts" + provides: "handoffSchema and messageType in createMessageSchema" + contains: "handoffSchema" + key_links: + - from: "packages/db/src/schema/chat_messages.ts" + to: "packages/shared/src/types/chat.ts" + via: "messageType field must match" + pattern: "messageType" +--- + + +Foundation: DB migration for message_type column, shared types/validators extension, and Wave 0 test stubs for all Phase 23 components. + +Purpose: Every subsequent plan depends on the message_type column existing in the DB schema, the ChatMessage type having a messageType field, and test stubs being in place for TDD-style development. +Output: Migration file, updated schema/types/validators, test stub files. + + + +@.claude/get-shit-done/workflows/execute-plan.md +@.claude/get-shit-done/templates/summary.md + + + +@.planning/PROJECT.md +@.planning/ROADMAP.md +@.planning/STATE.md +@.planning/phases/23-brainstormer-flow/23-RESEARCH.md +@.planning/phases/23-brainstormer-flow/23-UI-SPEC.md + + + + +From packages/shared/src/types/chat.ts: +```typescript +export interface ChatMessage { + id: string; + conversationId: string; + role: "user" | "assistant" | "system"; + content: string; + agentId: string | null; + createdAt: string; + updatedAt: string | null; +} +``` + +From packages/shared/src/validators/chat.ts: +```typescript +export const createMessageSchema = z.object({ + role: z.enum(["user", "assistant", "system"]), + content: z.string().min(1).max(100_000), + agentId: z.string().uuid().optional(), +}); +``` + +From packages/db/src/schema/chat_messages.ts: +```typescript +export const chatMessages = pgTable( + "chat_messages", + { + id: uuid("id").primaryKey().defaultRandom(), + conversationId: uuid("conversation_id").notNull() + .references(() => chatConversations.id, { onDelete: "cascade" }), + role: text("role").notNull(), + content: text("content").notNull(), + agentId: uuid("agent_id"), + createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(), + updatedAt: timestamp("updated_at", { withTimezone: true }).defaultNow(), + }, + (table) => ({ + conversationCreatedIdx: index("chat_messages_conversation_created_idx").on(table.conversationId, table.createdAt), + }), +); +``` + +Migration journal: last entry is idx 46, tag "0046_smooth_sentinels". Files on disk go up to 0048. Next migration must be 0049. + + + + + + + Task 1: DB migration and shared types for message_type + + - packages/db/src/schema/chat_messages.ts + - packages/db/src/migrations/meta/_journal.json + - packages/shared/src/types/chat.ts + - packages/shared/src/validators/chat.ts + - packages/shared/src/index.ts + + + packages/db/src/schema/chat_messages.ts, + packages/db/src/migrations/0049_add_message_type.sql, + packages/db/src/migrations/meta/_journal.json, + packages/shared/src/types/chat.ts, + packages/shared/src/validators/chat.ts, + packages/shared/src/index.ts + + +1. Create migration file `packages/db/src/migrations/0049_add_message_type.sql`: + ```sql + ALTER TABLE "chat_messages" ADD COLUMN "message_type" text; + ``` + +2. Update `packages/db/src/migrations/meta/_journal.json`: append a new entry after idx 46 with idx 49, version "7", tag "0049_add_message_type", breakpoints true. Use `when: Date.now()` (current epoch ms). + + IMPORTANT: The journal has entries up to idx 46 but disk has files 0047 and 0048 (added manually in Phases 21/22 without journal entries). Add ONLY the idx 49 entry. Do NOT add entries for 0047 or 0048 — those were applied outside the journal. + + Wait — re-read the journal. If _journal.json only goes to idx 46, but files go to 0048, we must add entries for 47, 48, AND 49 to keep the journal consistent. Read the actual SQL files 0047 and 0048 to determine their tags. + + Actually, looking at the research notes: "The migration numbering... The next migration must be named 0049_*.sql with idx 49 added to _journal.json." Follow this guidance — add idx 47 (tag "0047_nebulous_klaw"), idx 48 (tag "0048_add_chat_messages_updated_at"), and idx 49 (tag "0049_add_message_type") entries. + +3. Update `packages/db/src/schema/chat_messages.ts`: add `messageType: text("message_type"),` after the `agentId` field. This is a nullable text column. Values: null (normal), "handoff", "spec_card", "task_created", "status_update". + +4. Update `packages/shared/src/types/chat.ts`: + - Add `messageType: string | null;` to the `ChatMessage` interface (after `agentId`). + +5. Update `packages/shared/src/validators/chat.ts`: + - Add `messageType: z.string().optional(),` to `createMessageSchema`. + - Add new `handoffSchema`: + ```typescript + export const handoffSchema = z.object({ + spec: z.object({ + what: z.string().min(1), + why: z.string().min(1), + constraints: z.string().optional().default(""), + success: z.string().optional().default(""), + }), + targetRole: z.enum(["pm", "engineer", "general"]), + }); + export type Handoff = z.infer; + ``` + - Add `handoffSchema` and `Handoff` to the file's exports. + +6. Update `packages/shared/src/index.ts`: add re-exports for `handoffSchema` and `Handoff` type from validators/chat.ts. Follow the existing pattern of re-exporting from `./validators/chat.js`. + + + cd /opt/nexus && pnpm exec tsc --noEmit -p packages/shared/tsconfig.json 2>&1 | head -20 && pnpm exec tsc --noEmit -p packages/db/tsconfig.json 2>&1 | head -20 + + + - grep -q "messageType" packages/db/src/schema/chat_messages.ts + - grep -q "message_type" packages/db/src/migrations/0049_add_message_type.sql + - grep -q "messageType" packages/shared/src/types/chat.ts + - grep -q "handoffSchema" packages/shared/src/validators/chat.ts + - grep -q "handoffSchema" packages/shared/src/index.ts + - grep -q "0049_add_message_type" packages/db/src/migrations/meta/_journal.json + + message_type column defined in Drizzle schema and SQL migration; ChatMessage type and createMessageSchema include messageType; handoffSchema exported from shared package + + + + Task 2: Wave 0 test stubs for Phase 23 components and hooks + + - ui/src/components/ChatMessage.test.tsx + - ui/src/components/ChatMessageList.test.tsx + + + ui/src/components/ChatSpecCard.test.tsx, + ui/src/components/ChatHandoffIndicator.test.tsx, + ui/src/components/ChatTaskCreatedBadge.test.tsx, + ui/src/components/ChatStatusUpdateBadge.test.tsx, + ui/src/hooks/useBrainstormerDefault.test.ts + + +Create test stub files using it.todo() pattern (matching Phase 21 convention — NOT it.skip()): + +1. `ui/src/components/ChatSpecCard.test.tsx`: + ```tsx + // @vitest-environment jsdom + import { describe, it } from "vitest"; + + describe("ChatSpecCard", () => { + it.todo("renders four spec sections: What, Why, Constraints, Success"); + it.todo("parses JSON content and displays field values"); + it.todo("shows error fallback on JSON parse failure"); + it.todo("Send to PM button calls onHandoff with spec content"); + it.todo("Edit button switches to textarea edit mode"); + it.todo("Save changes button disabled when all fields empty"); + it.todo("Discard button reverts to read-only mode"); + it.todo("Save as Draft button adds [Draft] badge"); + it.todo("renders with role=region and aria-label=Specification"); + }); + ``` + +2. `ui/src/components/ChatHandoffIndicator.test.tsx`: + ```tsx + // @vitest-environment jsdom + import { describe, it } from "vitest"; + + describe("ChatHandoffIndicator", () => { + it.todo("renders content text between two hr elements"); + it.todo("has aria-label for agent handoff"); + it.todo("hr elements have aria-hidden=true"); + }); + ``` + +3. `ui/src/components/ChatTaskCreatedBadge.test.tsx`: + ```tsx + // @vitest-environment jsdom + import { describe, it } from "vitest"; + + describe("ChatTaskCreatedBadge", () => { + it.todo("shows Creating task... when taskId is not provided"); + it.todo("renders taskId, taskTitle, and View task link when resolved"); + it.todo("View task link has correct aria-label"); + it.todo("has role=status on container"); + }); + ``` + +4. `ui/src/components/ChatStatusUpdateBadge.test.tsx`: + ```tsx + // @vitest-environment jsdom + import { describe, it } from "vitest"; + + describe("ChatStatusUpdateBadge", () => { + it.todo("renders CheckCircle2 icon, agent name, and task reference"); + it.todo("View task link navigates to issue detail"); + it.todo("has role=status on container"); + }); + ``` + +5. `ui/src/hooks/useBrainstormerDefault.test.ts`: + ```ts + // @vitest-environment jsdom + import { describe, it } from "vitest"; + + describe("useBrainstormerDefault", () => { + it.todo("returns general role agent ID when available"); + it.todo("returns first by createdAt when multiple general agents exist"); + it.todo("returns null when no agents loaded"); + it.todo("returns null when no general agent exists"); + }); + ``` + + + cd /opt/nexus && pnpm vitest run --project=ui -- ChatSpecCard ChatHandoffIndicator ChatTaskCreatedBadge ChatStatusUpdateBadge useBrainstormerDefault 2>&1 | tail -10 + + + - test -f ui/src/components/ChatSpecCard.test.tsx + - test -f ui/src/components/ChatHandoffIndicator.test.tsx + - test -f ui/src/components/ChatTaskCreatedBadge.test.tsx + - test -f ui/src/components/ChatStatusUpdateBadge.test.tsx + - test -f ui/src/hooks/useBrainstormerDefault.test.ts + - grep -q "it.todo" ui/src/components/ChatSpecCard.test.tsx + - grep -q "it.todo" ui/src/hooks/useBrainstormerDefault.test.ts + + All 5 test stub files exist with it.todo() entries covering every Phase 23 behavior; vitest run finds them without errors + + + + + +- `pnpm exec tsc --noEmit` passes for shared and db packages +- Migration file 0049 exists with correct SQL +- Journal updated with idx 49 entry +- All 5 test stub files parseable by vitest + + + +- messageType column in Drizzle schema and SQL migration +- ChatMessage type has messageType field +- handoffSchema exported from shared +- createMessageSchema accepts optional messageType +- 5 test stub files with it.todo() entries + + + +After completion, create `.planning/phases/23-brainstormer-flow/23-00-SUMMARY.md` + diff --git a/.planning/phases/23-brainstormer-flow/23-01-PLAN.md b/.planning/phases/23-brainstormer-flow/23-01-PLAN.md new file mode 100644 index 00000000..bc87d81a --- /dev/null +++ b/.planning/phases/23-brainstormer-flow/23-01-PLAN.md @@ -0,0 +1,285 @@ +--- +phase: 23-brainstormer-flow +plan: 01 +type: execute +wave: 1 +depends_on: ["23-00"] +files_modified: + - server/src/services/chat.ts + - server/src/routes/chat.ts +autonomous: true +requirements: + - AGENT-03 + - AGENT-06 + - AGENT-07 + - CHAT-09 + +must_haves: + truths: + - "POST /conversations/:id/handoff inserts a handoff system message and creates issues" + - "POST /conversations/:id/status-update inserts a status_update system message" + - "addMessage accepts optional messageType and persists it" + - "addSystemMessage helper creates system role messages with messageType" + artifacts: + - path: "server/src/services/chat.ts" + provides: "addSystemMessage helper and messageType support in addMessage" + contains: "addSystemMessage" + - path: "server/src/routes/chat.ts" + provides: "handoff and status-update routes" + contains: "handoff" + key_links: + - from: "server/src/routes/chat.ts" + to: "server/src/services/chat.ts" + via: "svc.addSystemMessage call" + pattern: "svc\\.addSystemMessage" + - from: "server/src/routes/chat.ts" + to: "server/src/routes/issues.ts" + via: "issueService for task creation from handoff" + pattern: "issueSvc\\.create" +--- + + +Server-side: extend chat service with addSystemMessage helper, messageType support in addMessage, and add handoff + status-update routes. + +Purpose: The handoff route is the backbone of the Brainstormer-to-PM flow. The status-update route enables agent completion notifications in chat. Both insert typed system messages. +Output: Extended chat service, two new routes on chatRoutes. + + + +@.claude/get-shit-done/workflows/execute-plan.md +@.claude/get-shit-done/templates/summary.md + + + +@.planning/PROJECT.md +@.planning/ROADMAP.md +@.planning/STATE.md +@.planning/phases/23-brainstormer-flow/23-RESEARCH.md +@.planning/phases/23-brainstormer-flow/23-00-SUMMARY.md + + + + +From server/src/services/chat.ts: +```typescript +export function chatService(db: Db) { + return { + async addMessage(conversationId: string, data: { role: string; content: string; agentId?: string }) { ... }, + async getConversation(id: string) { ... }, // returns full row including companyId + async editMessage(messageId: string, content: string) { ... }, + // ... other methods + }; +} +``` + +From server/src/routes/chat.ts: +```typescript +export function chatRoutes(db: Db): Router { + const router = Router(); + const svc = chatService(db); + // Routes: GET/POST conversations, GET/PATCH/DELETE conversations/:id, + // GET/POST messages, POST stream, PATCH messages/:msgId, DELETE messages/after/:msgId +} +``` + +From packages/shared/src/validators/chat.ts (after Plan 00): +```typescript +export const handoffSchema = z.object({ + spec: z.object({ what: z.string().min(1), why: z.string().min(1), constraints: z.string().optional().default(""), success: z.string().optional().default("") }), + targetRole: z.enum(["pm", "engineer", "general"]), +}); +``` + +From packages/shared/src/validators/issue.ts: +```typescript +export const createIssueSchema = z.object({ + title: z.string().min(1), + description: z.string().optional().nullable(), + status: z.enum(ISSUE_STATUSES).optional().default("backlog"), + priority: z.enum(ISSUE_PRIORITIES).optional().default("medium"), + // ... many optional fields +}); +``` + +Issue creation pattern: `issueService(db).create(companyId, { title, description, ... })` returns `{ id, identifier, title, ... }`. + +From server/src/routes/issues.ts (line 964): +```typescript +router.post("/companies/:companyId/issues", validate(createIssueSchema), async (req, res) => { + const issue = await svc.create(companyId, { ...req.body, createdByAgentId: actor.agentId, createdByUserId: ... }); + res.status(201).json(issue); +}); +``` + +The chatRoutes function currently receives only `db: Db`. To call issueService, either: +(a) Import and instantiate issueService inside chatRoutes, or +(b) Add issueService as a parameter to chatRoutes. + +Option (a) is simplest and matches how heartbeat.ts instantiates issueService locally. + + + + + + + Task 1: Extend chat service with messageType support and addSystemMessage + + - server/src/services/chat.ts + - packages/db/src/schema/chat_messages.ts + + server/src/services/chat.ts + +1. Extend `addMessage` to accept optional `messageType?: string` in its data parameter. Pass `messageType: data.messageType ?? null` in the `.values()` call to `chatMessages`. This requires the schema from Plan 00 to include the messageType column. + +2. Add `addSystemMessage` helper method to the returned service object: + ```typescript + async addSystemMessage( + conversationId: string, + data: { content: string; messageType: string; agentId?: string }, + ) { + const [message] = await db + .insert(chatMessages) + .values({ + conversationId, + role: "system", + content: data.content, + agentId: data.agentId ?? null, + messageType: data.messageType, + }) + .returning(); + + // Bump conversation updatedAt (same pattern as addMessage Pitfall 3) + await db + .update(chatConversations) + .set({ updatedAt: new Date() }) + .where(eq(chatConversations.id, conversationId)); + + return message!; + }, + ``` + +3. Also extend `listMessages` return — the `messageType` field will automatically be included in `select()` results since the Drizzle schema now defines it. No change needed in listMessages itself, but verify the return rows will include `messageType`. + + + cd /opt/nexus && pnpm exec tsc --noEmit -p server/tsconfig.json 2>&1 | head -20 + + + - grep -q "addSystemMessage" server/src/services/chat.ts + - grep -q "messageType" server/src/services/chat.ts + + addMessage accepts messageType; addSystemMessage creates system messages with typed messageType; both bump conversation updatedAt + + + + Task 2: Add handoff and status-update routes + + - server/src/routes/chat.ts + - server/src/services/issues.ts (first 20 lines + the create method signature) + - server/src/routes/issues.ts (lines 964-1001 for create pattern) + - packages/shared/src/validators/chat.ts + + server/src/routes/chat.ts + +1. Import `handoffSchema` from `@paperclipai/shared` at the top of chat.ts. + +2. Import `issueService` from `../services/issues.js` at the top. + +3. Inside `chatRoutes(db)`, instantiate: `const issueSvc = issueService(db);` + +4. Add `POST /conversations/:id/handoff` route (before the `return router` line): + ```typescript + router.post("/conversations/:id/handoff", async (req, res) => { + assertBoard(req); + const data = handoffSchema.parse(req.body); + + // Resolve companyId from conversation (Pitfall 4) + const conversation = await svc.getConversation(req.params.id!); + const companyId = conversation.companyId; + + // 1. Insert handoff system message + const handoffMsg = await svc.addSystemMessage(req.params.id!, { + content: `Brainstormer \u2192 PM: spec handed off`, + messageType: "handoff", + }); + + // 2. Create issue from spec + const specDescription = [ + `**What:** ${data.spec.what}`, + `**Why:** ${data.spec.why}`, + data.spec.constraints ? `**Constraints:** ${data.spec.constraints}` : "", + data.spec.success ? `**Success:** ${data.spec.success}` : "", + ].filter(Boolean).join("\n\n"); + + const issue = await issueSvc.create(companyId, { + title: data.spec.what.slice(0, 100), + description: specDescription, + status: "backlog", + priority: "medium", + }); + + // 3. Insert task_created system message + await svc.addSystemMessage(req.params.id!, { + content: JSON.stringify({ + taskId: issue.identifier, + taskTitle: issue.title, + taskUrl: `/issues/${issue.id}`, + }), + messageType: "task_created", + }); + + res.json({ handoffMessageId: handoffMsg.id, issues: [issue] }); + }); + ``` + +5. Add `POST /conversations/:id/status-update` route: + ```typescript + router.post("/conversations/:id/status-update", async (req, res) => { + assertBoard(req); + const { agentName, taskId, taskTitle, taskUrl } = req.body; + if (!agentName || !taskId) { + res.status(400).json({ error: "agentName and taskId are required" }); + return; + } + + const message = await svc.addSystemMessage(req.params.id!, { + content: JSON.stringify({ agentName, taskId, taskTitle, taskUrl }), + messageType: "status_update", + }); + + res.status(201).json(message); + }); + ``` + +IMPORTANT: The `issueService` import path uses `.js` extension (ESM convention in this codebase). Check the existing imports in chat.ts and issues.ts for the exact pattern. The server uses `"../services/issues.js"` style imports. + + + cd /opt/nexus && pnpm exec tsc --noEmit -p server/tsconfig.json 2>&1 | head -20 + + + - grep -q "handoff" server/src/routes/chat.ts + - grep -q "status-update" server/src/routes/chat.ts + - grep -q "issueService" server/src/routes/chat.ts + - grep -q "handoffSchema" server/src/routes/chat.ts + - grep -q "addSystemMessage" server/src/routes/chat.ts + + POST /conversations/:id/handoff creates handoff message + issue + task_created message; POST /conversations/:id/status-update creates status_update message; both routes use assertBoard for auth + + + + + +- `pnpm exec tsc --noEmit -p server/tsconfig.json` passes +- `pnpm vitest run --project=server` passes (existing tests not broken) + + + +- addSystemMessage helper exists in chat service +- addMessage accepts optional messageType +- Handoff route creates handoff + task_created system messages and an issue +- Status-update route creates status_update system message +- TypeScript compilation clean + + + +After completion, create `.planning/phases/23-brainstormer-flow/23-01-SUMMARY.md` + diff --git a/.planning/phases/23-brainstormer-flow/23-02-PLAN.md b/.planning/phases/23-brainstormer-flow/23-02-PLAN.md new file mode 100644 index 00000000..73ded019 --- /dev/null +++ b/.planning/phases/23-brainstormer-flow/23-02-PLAN.md @@ -0,0 +1,370 @@ +--- +phase: 23-brainstormer-flow +plan: 02 +type: execute +wave: 1 +depends_on: ["23-00"] +files_modified: + - ui/src/components/ChatSpecCard.tsx + - ui/src/components/ChatHandoffIndicator.tsx + - ui/src/components/ChatTaskCreatedBadge.tsx + - ui/src/components/ChatStatusUpdateBadge.tsx + - ui/src/hooks/useBrainstormerDefault.ts +autonomous: true +requirements: + - AGENT-01 + - AGENT-02 + - AGENT-05 + - AGENT-06 + - AGENT-07 + +must_haves: + truths: + - "ChatSpecCard renders spec sections and action buttons" + - "ChatSpecCard edit mode allows editing all four fields" + - "ChatHandoffIndicator renders as separator with flanking hr elements" + - "ChatTaskCreatedBadge shows loading state and resolved state" + - "ChatStatusUpdateBadge shows completion icon and task reference" + - "useBrainstormerDefault returns general role agent ID" + artifacts: + - path: "ui/src/components/ChatSpecCard.tsx" + provides: "Spec card with What/Why/Constraints/Success fields and action buttons" + exports: ["ChatSpecCard"] + - path: "ui/src/components/ChatHandoffIndicator.tsx" + provides: "Separator-style handoff indicator" + exports: ["ChatHandoffIndicator"] + - path: "ui/src/components/ChatTaskCreatedBadge.tsx" + provides: "Task created inline badge" + exports: ["ChatTaskCreatedBadge"] + - path: "ui/src/components/ChatStatusUpdateBadge.tsx" + provides: "Status update inline badge" + exports: ["ChatStatusUpdateBadge"] + - path: "ui/src/hooks/useBrainstormerDefault.ts" + provides: "Hook returning general agent ID for auto-selection" + exports: ["useBrainstormerDefault"] + key_links: + - from: "ui/src/hooks/useBrainstormerDefault.ts" + to: "ui/src/api/agents.ts" + via: "useQuery with agents queryKey" + pattern: 'queryKey.*agents' +--- + + +Build all five new UI components and the useBrainstormerDefault hook for Phase 23. + +Purpose: These components render the four structured message types (spec_card, handoff, task_created, status_update) and provide the brainstormer default agent selection. Plan 03 wires them into ChatMessage dispatch. +Output: 4 new components + 1 new hook, all independently testable. + + + +@.claude/get-shit-done/workflows/execute-plan.md +@.claude/get-shit-done/templates/summary.md + + + +@.planning/PROJECT.md +@.planning/ROADMAP.md +@.planning/STATE.md +@.planning/phases/23-brainstormer-flow/23-RESEARCH.md +@.planning/phases/23-brainstormer-flow/23-UI-SPEC.md +@.planning/phases/23-brainstormer-flow/23-00-SUMMARY.md + + + + +From ui/src/components/ChatMessage.tsx: +```typescript +interface ChatMessageProps { + id?: string; + role: "user" | "assistant" | "system"; + content: string; + agentName?: string | null; + agentIcon?: string | null; + agentRole?: AgentRole | null; + timestamp?: string; + isStreaming?: boolean; + isAnyStreaming?: boolean; + onEdit?: (messageId: string, newContent: string) => void; + onRetry?: (messageId: string) => void; +} +``` + +From ui/src/api/chat.ts: +```typescript +export const chatApi = { + editMessage(conversationId: string, messageId: string, content: string) { ... }, + // Will need: handoffSpec(conversationId, spec, targetRole) — added in Plan 03 +}; +``` + +From ui/src/api/issues.ts: +```typescript +export const issuesApi = { + create: (companyId: string, data: Record) => api.post(`/companies/${companyId}/issues`, data), +}; +``` + +Existing shadcn components available: button, card, textarea (all installed). +Lucide icons needed: CheckCircle2, Brain (from lucide-react ^0.574.0, already installed). + +From agent-role-colors.ts: general role maps to text-slate-600 dark:text-slate-400. + + + + + + + Task 1: ChatSpecCard and ChatHandoffIndicator components + + - ui/src/components/ChatMessage.tsx + - ui/src/components/ChatMessageIdentityBar.tsx + - .planning/phases/23-brainstormer-flow/23-UI-SPEC.md (Spec Card Layout section and Handoff Indicator section) + + + ui/src/components/ChatSpecCard.tsx, + ui/src/components/ChatHandoffIndicator.tsx + + +1. Create `ui/src/components/ChatSpecCard.tsx`: + +Props interface: +```typescript +interface ChatSpecCardProps { + content: string; // JSON string of SpecContent + messageId?: string; + conversationId?: string; + onHandoff?: (spec: SpecContent) => void; +} + +interface SpecContent { + what: string; + why: string; + constraints: string; + success: string; +} +``` + +Implementation: +- Parse `content` via `JSON.parse` in a try/catch. On failure, render: `
Could not render spec.
` +- Container: `role="region" aria-label="Specification"` with `className="motion-safe:animate-in motion-safe:fade-in motion-safe:slide-in-from-bottom-2 rounded-lg border border-border bg-card p-4 max-w-[480px]"` +- Four sections, each with: + - Label: `

What

` (and Why, Constraints, Success) + - Content: `

{spec.what}

` + - Sections wrapped in `
` +- Action row: `
` + - "Send to PM" button: `variant="default" size="sm"` — calls `onHandoff?.(spec)`, disables during submission with `aria-disabled="true"` and `aria-busy="true"` on container + - "Edit" button: `variant="outline" size="sm"` — toggles local `isEditing` state + - "Save as Draft" button: `variant="ghost" size="sm"` — sets local `isDraft` state, adds "[Draft]" badge +- Edit mode (when `isEditing === true`): + - Each field becomes a `