docs(04): create phase plan

This commit is contained in:
2026-03-15 16:27:47 +01:00
parent 80496b7f50
commit 92afac3eb7
3 changed files with 444 additions and 3 deletions

View File

@@ -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 | - |

View File

@@ -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"
---
<objective>
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.
</objective>
<execution_context>
@/home/jean-luc-makiola/.claude/get-shit-done/workflows/execute-plan.md
@/home/jean-luc-makiola/.claude/get-shit-done/templates/summary.md
</execution_context>
<context>
@.planning/ROADMAP.md
@.planning/phases/04-database-planning-fixes/04-CONTEXT.md
<interfaces>
<!-- Key types and contracts the executor needs -->
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
}
```
</interfaces>
</context>
<tasks>
<task type="auto">
<name>Task 1: Add categoryId to threads schema, Zod schemas, types, and test helper</name>
<files>src/db/schema.ts, src/shared/schemas.ts, src/shared/types.ts, tests/helpers/db.ts</files>
<action>
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.
</action>
<verify>
<automated>cd /home/jean-luc-makiola/Development/projects/GearBox && bun run db:push 2>&1 | tail -5</automated>
</verify>
<done>threads table in schema.ts has categoryId with FK to categories, createThreadSchema requires categoryId, test helper CREATE TABLE matches, db:push succeeds</done>
</task>
<task type="auto">
<name>Task 2: Update thread service and routes to handle categoryId, update hook types</name>
<files>src/server/services/thread.service.ts, src/server/routes/threads.ts, src/client/hooks/useThreads.ts</files>
<action>
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.
</action>
<verify>
<automated>cd /home/jean-luc-makiola/Development/projects/GearBox && bun test 2>&1 | tail -20</automated>
</verify>
<done>Thread creation accepts categoryId, getAllThreads returns category name and emoji for each thread, existing tests pass, useCreateThread hook sends categoryId</done>
</task>
</tasks>
<verification>
- `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)
</verification>
<success_criteria>
- 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
</success_criteria>
<output>
After completion, create `.planning/phases/04-database-planning-fixes/04-01-SUMMARY.md`
</output>

View File

@@ -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"
---
<objective>
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.
</objective>
<execution_context>
@/home/jean-luc-makiola/.claude/get-shit-done/workflows/execute-plan.md
@/home/jean-luc-makiola/.claude/get-shit-done/templates/summary.md
</execution_context>
<context>
@.planning/ROADMAP.md
@.planning/phases/04-database-planning-fixes/04-CONTEXT.md
@.planning/phases/04-database-planning-fixes/04-01-SUMMARY.md
<interfaces>
<!-- From Plan 01: updated types the executor will consume -->
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[]>;
// 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
```
</interfaces>
</context>
<tasks>
<task type="auto">
<name>Task 1: Create thread modal and update uiStore</name>
<files>src/client/stores/uiStore.ts, src/client/components/CreateThreadModal.tsx</files>
<action>
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).
</action>
<verify>
<automated>cd /home/jean-luc-makiola/Development/projects/GearBox && bun run lint 2>&1 | tail -5</automated>
</verify>
<done>CreateThreadModal component renders a modal with name input and category dropdown, submits via useCreateThread, uiStore has createThreadModalOpen state</done>
</task>
<task type="auto">
<name>Task 2: Overhaul PlanningView with empty state, pill tabs, category filter, and thread card category display</name>
<files>src/client/routes/collection/index.tsx, src/client/components/ThreadCard.tsx</files>
<action>
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 `<select>` dropdown with "All categories" as default option, then each category with emoji + name. Filter threads client-side by matching `thread.categoryId === categoryFilter`.
c. **Thread list or empty state:**
- Pass `activeTab === "resolved"` as `includeResolved` to `useThreads`. When `activeTab === "active"`, show only active threads. When `activeTab === "resolved"`, filter the results to show only resolved threads (since `includeResolved=true` returns both).
- Apply `categoryFilter` on the client side if set.
d. **Empty state (when filtered threads array is empty AND activeTab is "active" AND no category filter):**
- Guided + educational tone per user decision.
- Max-width container (max-w-lg mx-auto), centered, py-16.
- Heading: "Plan your next purchase" (text-xl font-semibold).
- Three illustrated steps showing the workflow, each as a row with a step number circle (1, 2, 3), a short title, and a description:
1. "Create a thread" -- "Start a research thread for gear you're considering"
2. "Add candidates" -- "Add products you're comparing with prices and weights"
3. "Pick a winner" -- "Resolve the thread and the winner joins your collection"
- Style each step: flex row, step number in a 8x8 rounded-full bg-blue-100 text-blue-700 font-bold circle, title in font-medium, description in text-sm text-gray-500.
- CTA button below steps: "Create your first thread" -- calls `openCreateThreadModal()`. Blue-600 bg, white text, same style as collection empty state button.
- If empty because of active filter (category or "resolved" tab), show a simpler "No threads found" message instead of the full educational empty state.
e. **Render `<CreateThreadModal />` at the bottom** of PlanningView (it reads its own open/close state from uiStore).
f. **Thread grid:** Keep existing `grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4`. Pass `categoryName` and `categoryEmoji` as new props to ThreadCard.
</action>
<verify>
<automated>cd /home/jean-luc-makiola/Development/projects/GearBox && bun run lint 2>&1 | tail -5</automated>
</verify>
<done>PlanningView shows educational empty state with 3-step workflow, pill tabs for Active/Resolved, category filter dropdown, "New Thread" button opens modal, ThreadCard shows category badge, inline form is removed</done>
</task>
<task type="checkpoint:human-verify" gate="blocking">
<files>src/client/routes/collection/index.tsx</files>
<name>Task 3: Verify planning tab overhaul</name>
<what-built>Complete planning tab overhaul: thread creation modal, educational empty state, Active/Resolved pill tabs, category filter, and category display on thread cards.</what-built>
<how-to-verify>
1. Start both dev servers: `bun run dev:server` and `bun run dev:client`
2. Visit http://localhost:5173/collection?tab=planning
3. Verify the educational empty state appears with 3 illustrated steps and a "Create your first thread" CTA button
4. Click "Create your first thread" -- a modal should open with name input and category dropdown
5. Create a thread (enter a name, select a category, submit)
6. Verify the thread appears as a card with category emoji + name badge
7. Verify the "New Thread" button appears in the header area
8. Create a second thread in a different category
9. Test the category filter dropdown -- filtering should show only matching threads
10. Test the Active/Resolved pill tabs -- should toggle between active and resolved views
</how-to-verify>
<resume-signal>Type "approved" or describe issues</resume-signal>
<action>Human verifies the planning tab UI overhaul by testing the complete flow in browser.</action>
<verify>
<automated>cd /home/jean-luc-makiola/Development/projects/GearBox && bun run lint 2>&1 | tail -5</automated>
</verify>
<done>User confirms: empty state shows 3-step workflow, modal creates threads with category, pill tabs filter Active/Resolved, category filter works, thread cards show category</done>
</task>
</tasks>
<verification>
- `bun run lint` passes with no errors
- Planning tab shows educational empty state when no threads exist
- Thread creation modal opens from both empty state CTA and header button
- Creating a thread with name + category succeeds and thread appears in list
- Thread cards show category emoji and name
- Active/Resolved pill tabs filter correctly
- Category filter narrows the thread list
</verification>
<success_criteria>
- Inline thread creation form is replaced with modal dialog
- Empty state educates users about the 3-step planning workflow
- Active/Resolved pill tabs replace the "Show archived" checkbox
- Category filter allows narrowing thread list by category
- Thread cards display category information
- No lint errors
</success_criteria>
<output>
After completion, create `.planning/phases/04-database-planning-fixes/04-02-SUMMARY.md`
</output>