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) <noreply@anthropic.com>
12 KiB
| phase | plan | type | wave | depends_on | files_modified | autonomous | requirements | must_haves | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 23-brainstormer-flow | 00 | execute | 0 |
|
true |
|
|
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.
<execution_context> @.claude/get-shit-done/workflows/execute-plan.md @.claude/get-shit-done/templates/summary.md </execution_context>
@.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.mdFrom packages/shared/src/types/chat.ts:
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:
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:
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; ```-
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. Usewhen: 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.
-
Update
packages/db/src/schema/chat_messages.ts: addmessageType: text("message_type"),after theagentIdfield. This is a nullable text column. Values: null (normal), "handoff", "spec_card", "task_created", "status_update". -
Update
packages/shared/src/types/chat.ts:- Add
messageType: string | null;to theChatMessageinterface (afteragentId).
- Add
-
Update
packages/shared/src/validators/chat.ts:- Add
messageType: z.string().optional(),tocreateMessageSchema. - Add new
handoffSchema: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<typeof handoffSchema>; - Add
handoffSchemaandHandoffto the file's exports.
- Add
-
Update
packages/shared/src/index.ts: add re-exports forhandoffSchemaandHandofftype 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 <acceptance_criteria>- 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 </acceptance_criteria> message_type column defined in Drizzle schema and SQL migration; ChatMessage type and createMessageSchema include messageType; handoffSchema exported from shared package
-
ui/src/components/ChatSpecCard.test.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"); }); -
ui/src/components/ChatHandoffIndicator.test.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"); }); -
ui/src/components/ChatTaskCreatedBadge.test.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"); }); -
ui/src/components/ChatStatusUpdateBadge.test.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"); }); -
ui/src/hooks/useBrainstormerDefault.test.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"); });
<success_criteria>
- 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 </success_criteria>