--- phase: 24-search-history-branching plan: 00 type: execute wave: 1 depends_on: [] files_modified: - packages/db/src/migrations/0050_add_branch_columns.sql - packages/db/src/migrations/0051_add_message_search_vector.sql - packages/db/src/migrations/0052_create_chat_message_bookmarks.sql - packages/db/src/migrations/meta/_journal.json - packages/db/src/schema/chat_conversations.ts - packages/db/src/schema/chat_messages.ts - packages/db/src/schema/chat_message_bookmarks.ts - packages/db/src/schema/index.ts - packages/shared/src/types/chat.ts - packages/shared/src/validators/chat.ts - packages/shared/src/index.ts - server/src/__tests__/chat-service.test.ts - server/src/__tests__/chat-routes.test.ts autonomous: true requirements: - CHAT-07 - CHAT-13 - CHAT-14 - HIST-04 - HIST-07 - HIST-08 - PERF-04 must_haves: truths: - "DB has parentConversationId and branchFromMessageId columns on chat_conversations" - "DB has content_search tsvector generated column with GIN index on chat_messages" - "DB has chat_message_bookmarks table with companyId + messageId columns" - "Shared types include ChatMessageSearchResult, ChatBookmark, ChatConversationWithBranch" - "Test stubs exist for searchMessages, toggleBookmark, branchConversation, exportConversation" artifacts: - path: "packages/db/src/migrations/0050_add_branch_columns.sql" provides: "Branch columns migration" contains: "parent_conversation_id" - path: "packages/db/src/migrations/0051_add_message_search_vector.sql" provides: "tsvector + GIN index migration" contains: "content_search" - path: "packages/db/src/migrations/0052_create_chat_message_bookmarks.sql" provides: "Bookmarks table migration" contains: "chat_message_bookmarks" - path: "packages/db/src/schema/chat_message_bookmarks.ts" provides: "Drizzle schema for bookmarks table" exports: ["chatMessageBookmarks"] - path: "packages/shared/src/types/chat.ts" provides: "Search result, bookmark, and branch types" contains: "ChatMessageSearchResult" key_links: - from: "packages/db/src/schema/chat_message_bookmarks.ts" to: "packages/db/src/schema/index.ts" via: "re-export" pattern: "chatMessageBookmarks" - from: "packages/shared/src/types/chat.ts" to: "packages/shared/src/index.ts" via: "re-export" pattern: "ChatMessageSearchResult" --- Create the DB migrations, Drizzle schema updates, shared types/validators, and Wave 0 test stubs for Phase 24. Purpose: Foundation layer — all subsequent plans depend on these schema changes and type definitions. Output: Three migrations applied, updated Drizzle schemas, shared types, and test scaffolding. @$HOME/.claude/get-shit-done/workflows/execute-plan.md @$HOME/.claude/get-shit-done/templates/summary.md @.planning/PROJECT.md @.planning/ROADMAP.md @.planning/STATE.md @.planning/phases/24-search-history-branching/24-RESEARCH.md @packages/db/src/schema/chat_conversations.ts @packages/db/src/schema/chat_messages.ts @packages/db/src/schema/index.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 @server/src/__tests__/chat-service.test.ts @server/src/__tests__/chat-routes.test.ts Task 1: DB migrations and Drizzle schema updates packages/db/src/migrations/0050_add_branch_columns.sql, packages/db/src/migrations/0051_add_message_search_vector.sql, packages/db/src/migrations/0052_create_chat_message_bookmarks.sql, packages/db/src/migrations/meta/_journal.json, packages/db/src/schema/chat_conversations.ts, packages/db/src/schema/chat_messages.ts, packages/db/src/schema/chat_message_bookmarks.ts, packages/db/src/schema/index.ts packages/db/src/schema/chat_conversations.ts, packages/db/src/schema/chat_messages.ts, packages/db/src/schema/index.ts, packages/db/src/migrations/meta/_journal.json, packages/db/src/schema/companies.ts **Migration 0050_add_branch_columns.sql:** ```sql ALTER TABLE "chat_conversations" ADD COLUMN "parent_conversation_id" uuid REFERENCES "chat_conversations"("id") ON DELETE SET NULL, ADD COLUMN "branch_from_message_id" uuid; CREATE INDEX "chat_conversations_parent_idx" ON "chat_conversations" ("parent_conversation_id"); ``` **Migration 0051_add_message_search_vector.sql:** ```sql ALTER TABLE "chat_messages" ADD COLUMN "content_search" tsvector GENERATED ALWAYS AS (to_tsvector('english', "content")) STORED; CREATE INDEX "chat_messages_content_search_idx" ON "chat_messages" USING GIN ("content_search"); ``` **Migration 0052_create_chat_message_bookmarks.sql:** ```sql CREATE TABLE "chat_message_bookmarks" ( "id" uuid PRIMARY KEY DEFAULT gen_random_uuid(), "company_id" uuid NOT NULL REFERENCES "companies"("id"), "message_id" uuid NOT NULL REFERENCES "chat_messages"("id") ON DELETE CASCADE, "conversation_id" uuid NOT NULL REFERENCES "chat_conversations"("id") ON DELETE CASCADE, "created_at" timestamp with time zone NOT NULL DEFAULT now() ); CREATE INDEX "chat_bookmarks_company_message_idx" ON "chat_message_bookmarks" ("company_id", "message_id"); CREATE INDEX "chat_bookmarks_company_conv_idx" ON "chat_message_bookmarks" ("company_id", "conversation_id"); ``` **Update _journal.json:** Add entries for idx 50, 51, 52 following existing format (version "7", breakpoints true). Use tags: `0050_add_branch_columns`, `0051_add_message_search_vector`, `0052_create_chat_message_bookmarks`. Use timestamp `1775200000000` for 0050, `+1000` for each subsequent. **Update chat_conversations.ts:** Add two columns after `updatedAt`: - `parentConversationId: uuid("parent_conversation_id").references(() => chatConversations.id, { onDelete: "set null" })` - `branchFromMessageId: uuid("branch_from_message_id")` Add index: `parentIdx: index("chat_conversations_parent_idx").on(table.parentConversationId)` **Update chat_messages.ts:** Do NOT add contentSearch to the Drizzle schema — it is a Postgres generated column referenced only via raw `sql` in queries. Add a comment: `// content_search tsvector column exists in Postgres (generated stored) — queried via sql\`\` only` **Create chat_message_bookmarks.ts:** New schema file following Pattern 3 from RESEARCH.md. Use object-syntax `(table) => ({})` for index callbacks (codebase convention). **Update schema/index.ts:** Add `export { chatMessageBookmarks } from "./chat_message_bookmarks.js";` at the end. **Run migrations:** `pnpm --filter @paperclipai/db db:push` or the project's migration command to apply. cd /opt/nexus && pnpm --filter @paperclipai/db build 2>&1 | tail -5 - grep -q "parent_conversation_id" packages/db/src/migrations/0050_add_branch_columns.sql - grep -q "content_search" packages/db/src/migrations/0051_add_message_search_vector.sql - grep -q "chat_message_bookmarks" packages/db/src/migrations/0052_create_chat_message_bookmarks.sql - grep -q "parentConversationId" packages/db/src/schema/chat_conversations.ts - grep -q "chatMessageBookmarks" packages/db/src/schema/chat_message_bookmarks.ts - grep -q "chatMessageBookmarks" packages/db/src/schema/index.ts - grep -q "0050" packages/db/src/migrations/meta/_journal.json Three migration SQL files exist, Drizzle schemas updated with branch columns and bookmark table, schema index exports chatMessageBookmarks, db package builds cleanly. Task 2: Shared types, validators, and Wave 0 test stubs packages/shared/src/types/chat.ts, packages/shared/src/validators/chat.ts, packages/shared/src/index.ts, server/src/__tests__/chat-service.test.ts, server/src/__tests__/chat-routes.test.ts packages/shared/src/types/chat.ts, packages/shared/src/validators/chat.ts, packages/shared/src/index.ts, server/src/__tests__/chat-service.test.ts, server/src/__tests__/chat-routes.test.ts **Add to packages/shared/src/types/chat.ts:** ```typescript export interface ChatMessageSearchResult { messageId: string; conversationId: string; conversationTitle: string | null; content: string; role: "user" | "assistant" | "system"; agentId: string | null; createdAt: string; rank: number; } export interface ChatMessageSearchResponse { items: ChatMessageSearchResult[]; } export interface ChatBookmark { id: string; companyId: string; messageId: string; conversationId: string; createdAt: string; } export interface ChatBookmarkWithMessage extends ChatBookmark { message: ChatMessage; conversationTitle: string | null; } export interface ChatBookmarkListResponse { items: ChatBookmarkWithMessage[]; } export interface ChatBookmarkToggleResponse { bookmarked: boolean; } ``` Add `parentConversationId: string | null` and `branchFromMessageId: string | null` to `ChatConversation` interface. Also add them to `ChatConversationListItem`. **Add to packages/shared/src/validators/chat.ts:** ```typescript export const searchMessagesSchema = z.object({ q: z.string().min(2).max(200), limit: z.coerce.number().int().min(1).max(50).optional(), }); export const branchConversationSchema = z.object({ branchFromMessageId: z.string().uuid(), }); export type SearchMessages = z.infer; export type BranchConversation = z.infer; ``` **Update packages/shared/src/index.ts:** Re-export the new types (`ChatMessageSearchResult`, `ChatMessageSearchResponse`, `ChatBookmark`, `ChatBookmarkWithMessage`, `ChatBookmarkListResponse`, `ChatBookmarkToggleResponse`) and validators (`searchMessagesSchema`, `branchConversationSchema`, `SearchMessages`, `BranchConversation`). **Add Wave 0 test stubs to chat-service.test.ts:** Add four new `describe` blocks at the end of the file: - `describe("searchMessages", () => { it.todo("returns ranked results for matching term"); it.todo("returns empty for no match"); it.todo("respects companyId scope"); })` - `describe("toggleBookmark", () => { it.todo("creates bookmark when not exists"); it.todo("removes bookmark when exists"); })` - `describe("branchConversation", () => { it.todo("creates child conversation with copied messages"); it.todo("throws not found for invalid message id"); })` - `describe("exportConversation", () => { it.todo("exports as markdown with agent names"); it.todo("exports as JSON with all messages"); })` **Add Wave 0 test stubs to chat-routes.test.ts:** Add four new `describe` blocks: - `describe("GET /companies/:id/messages/search", () => { it.todo("returns 200 with search results"); it.todo("returns 400 for short query"); })` - `describe("POST /conversations/:id/bookmarks", () => { it.todo("toggles bookmark on/off"); })` - `describe("POST /conversations/:id/branch", () => { it.todo("returns 201 with branched conversation"); })` - `describe("GET /conversations/:id/export", () => { it.todo("returns markdown file download"); it.todo("returns JSON file download"); })` cd /opt/nexus && pnpm --filter @paperclipai/shared build 2>&1 | tail -5 - grep -q "ChatMessageSearchResult" packages/shared/src/types/chat.ts - grep -q "ChatBookmark" packages/shared/src/types/chat.ts - grep -q "parentConversationId" packages/shared/src/types/chat.ts - grep -q "searchMessagesSchema" packages/shared/src/validators/chat.ts - grep -q "branchConversationSchema" packages/shared/src/validators/chat.ts - grep -q "ChatMessageSearchResult" packages/shared/src/index.ts - grep -q "searchMessages" server/src/__tests__/chat-service.test.ts - grep -q "toggleBookmark" server/src/__tests__/chat-service.test.ts - grep -q "branchConversation" server/src/__tests__/chat-service.test.ts - grep -q "exportConversation" server/src/__tests__/chat-service.test.ts Shared types include search result, bookmark, and branch interfaces. Validators include searchMessagesSchema and branchConversationSchema. ChatConversation has parentConversationId + branchFromMessageId. Test stubs exist for all four service methods and four route groups. - `pnpm --filter @paperclipai/db build` passes - `pnpm --filter @paperclipai/shared build` passes - Migration SQL files contain correct DDL - Test stubs are `it.todo()` (not `it.skip()`) Three migration files exist with correct SQL. Drizzle schemas updated. Shared types exported. Wave 0 test stubs in place. Both packages build cleanly. After completion, create `.planning/phases/24-search-history-branching/24-00-SUMMARY.md`