From 92afac3eb791f00e804d2d4907730af85895fbab Mon Sep 17 00:00:00 2001 From: Jean-Luc Makiola Date: Sun, 15 Mar 2026 16:27:47 +0100 Subject: [PATCH] docs(04): create phase plan --- .planning/ROADMAP.md | 7 +- .../04-database-planning-fixes/04-01-PLAN.md | 203 +++++++++++++++ .../04-database-planning-fixes/04-02-PLAN.md | 237 ++++++++++++++++++ 3 files changed, 444 insertions(+), 3 deletions(-) create mode 100644 .planning/phases/04-database-planning-fixes/04-01-PLAN.md create mode 100644 .planning/phases/04-database-planning-fixes/04-02-PLAN.md diff --git a/.planning/ROADMAP.md b/.planning/ROADMAP.md index 510597c..a562b04 100644 --- a/.planning/ROADMAP.md +++ b/.planning/ROADMAP.md @@ -34,10 +34,11 @@ 1. Running database schema push creates the threads table (and any other missing tables) without errors 2. User can create a new planning thread from the planning tab and it appears in the thread list 3. User sees a clear, polished empty state with a call-to-action when no planning threads exist -**Plans**: TBD +**Plans**: 2 plans Plans: -- [ ] 04-01: TBD +- [ ] 04-01-PLAN.md — Database schema fix and backend thread API with categoryId +- [ ] 04-02-PLAN.md — Frontend planning tab overhaul (modal, empty state, pill tabs, category filter) ### Phase 5: Image Handling **Goal**: Users can see and manage gear images throughout the app @@ -76,6 +77,6 @@ Phases execute in numeric order: 4 -> 5 -> 6 | 1. Foundation and Collection | v1.0 | 4/4 | Complete | 2026-03-14 | | 2. Planning Threads | v1.0 | 3/3 | Complete | 2026-03-15 | | 3. Setups and Dashboard | v1.0 | 3/3 | Complete | 2026-03-15 | -| 4. Database & Planning Fixes | v1.1 | 0/? | Not started | - | +| 4. Database & Planning Fixes | v1.1 | 0/2 | Not started | - | | 5. Image Handling | v1.1 | 0/? | Not started | - | | 6. Category Icons | v1.1 | 0/? | Not started | - | diff --git a/.planning/phases/04-database-planning-fixes/04-01-PLAN.md b/.planning/phases/04-database-planning-fixes/04-01-PLAN.md new file mode 100644 index 0000000..578018c --- /dev/null +++ b/.planning/phases/04-database-planning-fixes/04-01-PLAN.md @@ -0,0 +1,203 @@ +--- +phase: 04-database-planning-fixes +plan: 01 +type: execute +wave: 1 +depends_on: [] +files_modified: + - src/db/schema.ts + - src/shared/schemas.ts + - src/shared/types.ts + - src/server/services/thread.service.ts + - src/server/routes/threads.ts + - src/client/hooks/useThreads.ts + - tests/helpers/db.ts +autonomous: true +requirements: [DB-01, PLAN-01] + +must_haves: + truths: + - "Database schema push creates threads and thread_candidates tables without errors" + - "Threads table includes category_id column with foreign key to categories" + - "Creating a thread with name and categoryId succeeds via API" + - "getAllThreads returns categoryName and categoryEmoji for each thread" + artifacts: + - path: "src/db/schema.ts" + provides: "threads table with categoryId column" + contains: "categoryId.*references.*categories" + - path: "src/shared/schemas.ts" + provides: "createThreadSchema with categoryId field" + contains: "categoryId.*z.number" + - path: "src/server/services/thread.service.ts" + provides: "Thread CRUD with category join" + exports: ["createThread", "getAllThreads"] + - path: "tests/helpers/db.ts" + provides: "Test DB with category_id on threads" + contains: "category_id.*REFERENCES categories" + key_links: + - from: "src/server/routes/threads.ts" + to: "src/server/services/thread.service.ts" + via: "createThread(db, data) with categoryId" + pattern: "createThread.*data" + - from: "src/server/services/thread.service.ts" + to: "src/db/schema.ts" + via: "Drizzle insert/select on threads with categoryId" + pattern: "threads.*categoryId" +--- + + +Fix the missing threads table in the database and add categoryId to threads so thread creation works end-to-end. + +Purpose: DB-01 (threads table exists) and the backend half of PLAN-01 (thread creation works with category). Without this, the planning tab crashes on any thread operation. +Output: Working database schema, updated API that accepts categoryId on thread creation, and thread list returns category info. + + + +@/home/jean-luc-makiola/.claude/get-shit-done/workflows/execute-plan.md +@/home/jean-luc-makiola/.claude/get-shit-done/templates/summary.md + + + +@.planning/ROADMAP.md +@.planning/phases/04-database-planning-fixes/04-CONTEXT.md + + + + +From src/db/schema.ts (threads table -- needs categoryId added): +```typescript +export const threads = sqliteTable("threads", { + id: integer("id").primaryKey({ autoIncrement: true }), + name: text("name").notNull(), + status: text("status").notNull().default("active"), + resolvedCandidateId: integer("resolved_candidate_id"), + // MISSING: categoryId column + createdAt: integer("created_at", { mode: "timestamp" }).notNull().$defaultFn(() => new Date()), + updatedAt: integer("updated_at", { mode: "timestamp" }).notNull().$defaultFn(() => new Date()), +}); +``` + +From src/shared/schemas.ts (createThreadSchema -- needs categoryId): +```typescript +export const createThreadSchema = z.object({ + name: z.string().min(1, "Thread name is required"), + // MISSING: categoryId +}); +``` + +From src/client/hooks/useThreads.ts (ThreadListItem -- needs category fields): +```typescript +interface ThreadListItem { + id: number; + name: string; + status: "active" | "resolved"; + resolvedCandidateId: number | null; + createdAt: string; + updatedAt: string; + candidateCount: number; + minPriceCents: number | null; + maxPriceCents: number | null; + // MISSING: categoryId, categoryName, categoryEmoji +} +``` + + + + + + + Task 1: Add categoryId to threads schema, Zod schemas, types, and test helper + src/db/schema.ts, src/shared/schemas.ts, src/shared/types.ts, tests/helpers/db.ts + +1. In `src/db/schema.ts`, add `categoryId` column to the `threads` table: + ``` + categoryId: integer("category_id").notNull().references(() => categories.id), + ``` + Place it after the `resolvedCandidateId` field. + +2. In `src/shared/schemas.ts`, update `createThreadSchema` to require categoryId: + ``` + export const createThreadSchema = z.object({ + name: z.string().min(1, "Thread name is required"), + categoryId: z.number().int().positive(), + }); + ``` + Also update `updateThreadSchema` to allow optional categoryId: + ``` + export const updateThreadSchema = z.object({ + name: z.string().min(1).optional(), + categoryId: z.number().int().positive().optional(), + }); + ``` + +3. In `tests/helpers/db.ts`, update the threads CREATE TABLE to include `category_id`: + ```sql + CREATE TABLE threads ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + name TEXT NOT NULL, + status TEXT NOT NULL DEFAULT 'active', + resolved_candidate_id INTEGER, + category_id INTEGER NOT NULL REFERENCES categories(id), + created_at INTEGER NOT NULL DEFAULT (unixepoch()), + updated_at INTEGER NOT NULL DEFAULT (unixepoch()) + ) + ``` + +4. Run `bun run db:generate` to generate the migration for adding category_id to threads. +5. Run `bun run db:push` to apply the migration. + + + cd /home/jean-luc-makiola/Development/projects/GearBox && bun run db:push 2>&1 | tail -5 + + threads table in schema.ts has categoryId with FK to categories, createThreadSchema requires categoryId, test helper CREATE TABLE matches, db:push succeeds + + + + Task 2: Update thread service and routes to handle categoryId, update hook types + src/server/services/thread.service.ts, src/server/routes/threads.ts, src/client/hooks/useThreads.ts + +1. In `src/server/services/thread.service.ts`: + - Update `createThread` to insert `categoryId` from data: + `.values({ name: data.name, categoryId: data.categoryId })` + - Update `getAllThreads` to join with categories table and return `categoryId`, `categoryName`, `categoryEmoji` in the select: + ``` + categoryId: threads.categoryId, + categoryName: categories.name, + categoryEmoji: categories.emoji, + ``` + Add `.innerJoin(categories, eq(threads.categoryId, categories.id))` to the query. + - Update `updateThread` data type to include optional `categoryId: number`. + +2. In `src/server/routes/threads.ts`: + - The route handlers already pass `data` through from Zod validation, so createThread and updateThread should work with the updated schemas. Verify the PUT handler passes categoryId if present. + +3. In `src/client/hooks/useThreads.ts`: + - Add `categoryId: number`, `categoryName: string`, `categoryEmoji: string` to the `ThreadListItem` interface. + - Update `useCreateThread` mutationFn type to `{ name: string; categoryId: number }`. + +4. Run existing tests to confirm nothing breaks. + + + cd /home/jean-luc-makiola/Development/projects/GearBox && bun test 2>&1 | tail -20 + + Thread creation accepts categoryId, getAllThreads returns category name and emoji for each thread, existing tests pass, useCreateThread hook sends categoryId + + + + + +- `bun run db:push` completes without errors +- `bun test` passes all existing tests +- Start dev server (`bun run dev:server`) and confirm `curl http://localhost:3000/api/threads` returns 200 (empty array is fine) + + + +- threads table exists in database with category_id column +- POST /api/threads requires { name, categoryId } and creates a thread +- GET /api/threads returns threads with categoryName and categoryEmoji +- All existing tests pass + + + +After completion, create `.planning/phases/04-database-planning-fixes/04-01-SUMMARY.md` + diff --git a/.planning/phases/04-database-planning-fixes/04-02-PLAN.md b/.planning/phases/04-database-planning-fixes/04-02-PLAN.md new file mode 100644 index 0000000..ceba362 --- /dev/null +++ b/.planning/phases/04-database-planning-fixes/04-02-PLAN.md @@ -0,0 +1,237 @@ +--- +phase: 04-database-planning-fixes +plan: 02 +type: execute +wave: 2 +depends_on: [04-01] +files_modified: + - src/client/stores/uiStore.ts + - src/client/components/CreateThreadModal.tsx + - src/client/components/ThreadCard.tsx + - src/client/routes/collection/index.tsx +autonomous: false +requirements: [PLAN-01, PLAN-02] + +must_haves: + truths: + - "User can create a thread via a modal dialog with name and category fields" + - "User sees an inviting empty state explaining the 3-step planning workflow when no threads exist" + - "User can switch between Active and Resolved threads using pill tabs" + - "Thread cards display category icon and name" + artifacts: + - path: "src/client/components/CreateThreadModal.tsx" + provides: "Modal dialog for thread creation with name + category picker" + min_lines: 60 + - path: "src/client/routes/collection/index.tsx" + provides: "PlanningView with empty state, pill tabs, category filter, modal trigger" + contains: "CreateThreadModal" + - path: "src/client/components/ThreadCard.tsx" + provides: "Thread card with category display" + contains: "categoryEmoji" + key_links: + - from: "src/client/components/CreateThreadModal.tsx" + to: "src/client/hooks/useThreads.ts" + via: "useCreateThread mutation with { name, categoryId }" + pattern: "useCreateThread" + - from: "src/client/routes/collection/index.tsx" + to: "src/client/components/CreateThreadModal.tsx" + via: "createThreadModalOpen state from uiStore" + pattern: "CreateThreadModal" + - from: "src/client/components/ThreadCard.tsx" + to: "ThreadListItem" + via: "categoryName and categoryEmoji props" + pattern: "categoryEmoji|categoryName" +--- + + +Build the frontend for thread creation modal, polished empty state, Active/Resolved pill tabs, category filter, and category display on thread cards. + +Purpose: PLAN-01 (user can create threads without errors via modal) and PLAN-02 (polished empty state with CTA). This completes the planning tab UX overhaul. +Output: Working planning tab with modal-based thread creation, educational empty state, pill tab filtering, and category-aware thread cards. + + + +@/home/jean-luc-makiola/.claude/get-shit-done/workflows/execute-plan.md +@/home/jean-luc-makiola/.claude/get-shit-done/templates/summary.md + + + +@.planning/ROADMAP.md +@.planning/phases/04-database-planning-fixes/04-CONTEXT.md +@.planning/phases/04-database-planning-fixes/04-01-SUMMARY.md + + + + +From src/client/hooks/useThreads.ts (after Plan 01): +```typescript +interface ThreadListItem { + id: number; + name: string; + status: "active" | "resolved"; + resolvedCandidateId: number | null; + createdAt: string; + updatedAt: string; + candidateCount: number; + minPriceCents: number | null; + maxPriceCents: number | null; + categoryId: number; + categoryName: string; + categoryEmoji: string; +} + +// useCreateThread expects { name: string; categoryId: number } +``` + +From src/client/hooks/useCategories.ts: +```typescript +export function useCategories(): UseQueryResult; +// Category = { id: number; name: string; emoji: string; createdAt: Date } +``` + +From src/client/stores/uiStore.ts (needs createThreadModal state added): +```typescript +// Existing pattern for dialogs: +// resolveThreadId: number | null; +// openResolveDialog: (threadId, candidateId) => void; +// closeResolveDialog: () => void; +``` + +From src/client/routes/collection/index.tsx (CollectionView empty state pattern): +```typescript +// Lines 58-93: empty state with emoji, heading, description, CTA button +// Follow this pattern for planning empty state +``` + + + + + + + Task 1: Create thread modal and update uiStore + src/client/stores/uiStore.ts, src/client/components/CreateThreadModal.tsx + +1. In `src/client/stores/uiStore.ts`, add create-thread modal state following the existing dialog pattern: + ``` + createThreadModalOpen: boolean; + openCreateThreadModal: () => void; + closeCreateThreadModal: () => void; + ``` + Initialize `createThreadModalOpen: false` and wire up the actions. + +2. Create `src/client/components/CreateThreadModal.tsx`: + - A modal overlay (fixed inset-0, bg-black/50 backdrop, centered white panel) following the same pattern as the app's existing dialog styling. + - Form fields: Thread name (text input, required, min 1 char) and Category (select dropdown populated from `useCategories()` hook). + - Category select shows emoji + name for each option. Pre-select the first category. + - Submit calls `useCreateThread().mutate({ name, categoryId })`. + - On success: close modal (via `closeCreateThreadModal` from uiStore), reset form. + - On error: show inline error message. + - Cancel button and clicking backdrop closes modal. + - Disable submit button while `isPending`. + - Use Tailwind classes consistent with existing app styling (rounded-xl, text-sm, blue-600 primary buttons, gray-200 borders). + + + cd /home/jean-luc-makiola/Development/projects/GearBox && bun run lint 2>&1 | tail -5 + + CreateThreadModal component renders a modal with name input and category dropdown, submits via useCreateThread, uiStore has createThreadModalOpen state + + + + Task 2: Overhaul PlanningView with empty state, pill tabs, category filter, and thread card category display + src/client/routes/collection/index.tsx, src/client/components/ThreadCard.tsx + +1. In `src/client/components/ThreadCard.tsx`: + - Add `categoryName: string` and `categoryEmoji: string` props to `ThreadCardProps`. + - Display category as a pill badge (emoji + name) in the card's badge row, using a style like `bg-blue-50 text-blue-700` to distinguish from existing badges. + +2. In `src/client/routes/collection/index.tsx`, rewrite the `PlanningView` function: + + **Remove:** The inline text input + button form for thread creation. Remove the `showResolved` checkbox. + + **Add state:** + - `activeTab: "active" | "resolved"` (default "active") for the pill tab selector. + - `categoryFilter: number | null` (default null = all categories) for filtering. + - Import `useCategories` hook, `useUIStore`, and `CreateThreadModal`. + + **Layout (top to bottom):** + + a. **Header row:** "Planning Threads" heading on the left, "New Thread" button on the right. Button calls `openCreateThreadModal()` from uiStore. Use a plus icon (inline SVG, same pattern as collection empty state button). + + b. **Filter row:** Active/Resolved pill tab selector on the left, category filter dropdown on the right. + - Pill tabs: Two buttons styled as a segment control. Active pill gets `bg-blue-600 text-white`, inactive gets `bg-gray-100 text-gray-600 hover:bg-gray-200`. Rounded-full, px-4 py-1.5, text-sm font-medium. Wrap in a `flex bg-gray-100 rounded-full p-0.5 gap-0.5` container. + - Category filter: A `