From 67044f8f2e87843b73a96f7c8f86a9aeac3c33c2 Mon Sep 17 00:00:00 2001 From: Jean-Luc Makiola Date: Mon, 16 Mar 2026 21:26:59 +0100 Subject: [PATCH] docs(10): create phase plan --- .planning/ROADMAP.md | 6 +- .../10-01-PLAN.md | 302 ++++++++++++++++++ 2 files changed, 306 insertions(+), 2 deletions(-) create mode 100644 .planning/phases/10-schema-foundation-pros-cons-fields/10-01-PLAN.md diff --git a/.planning/ROADMAP.md b/.planning/ROADMAP.md index 532977a..7e00258 100644 --- a/.planning/ROADMAP.md +++ b/.planning/ROADMAP.md @@ -56,7 +56,9 @@ 2. User can save pros and cons text; the text persists across page refreshes 3. CandidateCard shows a visual indicator when a candidate has pros or cons entered 4. All existing tests pass after the schema migration (no column drift in test helper) -**Plans**: TBD +**Plans:** 1 plan +Plans: +- [ ] 10-01-PLAN.md — Add pros/cons fields through full stack (schema, service, Zod, form, card indicator) ### Phase 11: Candidate Ranking **Goal**: Users can drag candidates into a priority order that persists and is visually communicated @@ -104,7 +106,7 @@ | 7. Weight Unit Selection | v1.2 | 2/2 | Complete | 2026-03-16 | | 8. Search, Filter, and Candidate Status | v1.2 | 2/2 | Complete | 2026-03-16 | | 9. Weight Classification and Visualization | v1.2 | 2/2 | Complete | 2026-03-16 | -| 10. Schema Foundation + Pros/Cons Fields | v1.3 | 0/TBD | Not started | - | +| 10. Schema Foundation + Pros/Cons Fields | v1.3 | 0/1 | Not started | - | | 11. Candidate Ranking | v1.3 | 0/TBD | Not started | - | | 12. Comparison View | v1.3 | 0/TBD | Not started | - | | 13. Setup Impact Preview | v1.3 | 0/TBD | Not started | - | diff --git a/.planning/phases/10-schema-foundation-pros-cons-fields/10-01-PLAN.md b/.planning/phases/10-schema-foundation-pros-cons-fields/10-01-PLAN.md new file mode 100644 index 0000000..3ab8503 --- /dev/null +++ b/.planning/phases/10-schema-foundation-pros-cons-fields/10-01-PLAN.md @@ -0,0 +1,302 @@ +--- +phase: 10-schema-foundation-pros-cons-fields +plan: 01 +type: execute +wave: 1 +depends_on: [] +files_modified: + - src/db/schema.ts + - tests/helpers/db.ts + - src/server/services/thread.service.ts + - src/shared/schemas.ts + - src/client/hooks/useCandidates.ts + - src/client/components/CandidateForm.tsx + - src/client/components/CandidateCard.tsx + - src/client/routes/threads/$threadId.tsx + - tests/services/thread.service.test.ts +autonomous: true +requirements: [RANK-03] + +must_haves: + truths: + - "User can open a candidate edit form and see pros and cons text fields" + - "User can save pros and cons text; the text persists across page refreshes" + - "CandidateCard shows a visual indicator when a candidate has pros or cons entered" + - "All existing tests pass after the schema migration (no column drift in test helper)" + artifacts: + - path: "src/db/schema.ts" + provides: "pros and cons nullable TEXT columns on threadCandidates" + contains: "pros: text" + - path: "tests/helpers/db.ts" + provides: "Mirrored pros/cons columns in test DB CREATE TABLE" + contains: "pros TEXT" + - path: "src/server/services/thread.service.ts" + provides: "pros/cons in createCandidate, updateCandidate, getThreadWithCandidates" + contains: "pros:" + - path: "src/shared/schemas.ts" + provides: "pros and cons optional string fields in createCandidateSchema" + contains: "pros: z.string" + - path: "src/client/components/CandidateForm.tsx" + provides: "Pros and Cons textarea inputs in candidate form" + contains: "candidate-pros" + - path: "src/client/components/CandidateCard.tsx" + provides: "Visual indicator badge when pros or cons are present" + contains: "pros || cons" + - path: "tests/services/thread.service.test.ts" + provides: "Tests for pros/cons in create, update, and get operations" + contains: "pros" + key_links: + - from: "src/db/schema.ts" + to: "tests/helpers/db.ts" + via: "Manual column mirroring in CREATE TABLE" + pattern: "pros TEXT" + - from: "src/shared/schemas.ts" + to: "src/server/services/thread.service.ts" + via: "Zod-inferred CreateCandidate type used in service" + pattern: "CreateCandidate" + - from: "src/server/services/thread.service.ts" + to: "src/client/hooks/useCandidates.ts" + via: "API JSON response includes pros/cons fields" + pattern: "pros.*string.*null" + - from: "src/client/hooks/useCandidates.ts" + to: "src/client/components/CandidateForm.tsx" + via: "CandidateResponse type drives form pre-fill" + pattern: "candidate\\.pros" + - from: "src/client/routes/threads/$threadId.tsx" + to: "src/client/components/CandidateCard.tsx" + via: "Props threaded from candidate data to card" + pattern: "pros=.*candidate\\.pros" +--- + + +Add pros and cons annotation fields to thread candidates, from database through UI. + +Purpose: RANK-03 requires users to add pros/cons text per candidate for decision-making. This plan follows the established field-addition ladder: schema -> migration -> test helper -> service -> Zod -> types -> hook -> form -> card indicator. + +Output: Two new nullable TEXT columns (pros, cons) on thread_candidates, fully wired through all layers, with service-level tests and a visual indicator on CandidateCard. + + + +@/home/jlmak/.claude/get-shit-done/workflows/execute-plan.md +@/home/jlmak/.claude/get-shit-done/templates/summary.md + + + +@.planning/PROJECT.md +@.planning/ROADMAP.md +@.planning/STATE.md +@.planning/phases/10-schema-foundation-pros-cons-fields/10-RESEARCH.md + + + + +From src/db/schema.ts (threadCandidates table -- add pros/cons after status): +```typescript +export const threadCandidates = sqliteTable("thread_candidates", { + id: integer("id").primaryKey({ autoIncrement: true }), + threadId: integer("thread_id").notNull().references(() => threads.id, { onDelete: "cascade" }), + name: text("name").notNull(), + weightGrams: real("weight_grams"), + priceCents: integer("price_cents"), + categoryId: integer("category_id").notNull().references(() => categories.id), + notes: text("notes"), + productUrl: text("product_url"), + imageFilename: text("image_filename"), + status: text("status").notNull().default("researching"), + // ADD: pros: text("pros"), + // ADD: cons: text("cons"), + 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 (createCandidateSchema -- add optional pros/cons): +```typescript +export const createCandidateSchema = z.object({ + name: z.string().min(1, "Name is required"), + weightGrams: z.number().nonnegative().optional(), + priceCents: z.number().int().nonnegative().optional(), + categoryId: z.number().int().positive(), + notes: z.string().optional(), + productUrl: z.string().url().optional().or(z.literal("")), + imageFilename: z.string().optional(), + status: candidateStatusSchema.optional(), +}); +// updateCandidateSchema = createCandidateSchema.partial() -- inherits automatically +``` + +From src/server/services/thread.service.ts: +```typescript +// createCandidate: values() object needs pros/cons +// updateCandidate: inline Partial<{...}> type needs pros/cons +// getThreadWithCandidates: explicit .select({}) projection needs pros/cons +``` + +From src/client/hooks/useCandidates.ts: +```typescript +interface CandidateResponse { + id: number; threadId: number; name: string; + weightGrams: number | null; priceCents: number | null; + categoryId: number; notes: string | null; + productUrl: string | null; imageFilename: string | null; + status: "researching" | "ordered" | "arrived"; + createdAt: string; updatedAt: string; + // ADD: pros: string | null; + // ADD: cons: string | null; +} +``` + +From src/client/components/CandidateCard.tsx: +```typescript +interface CandidateCardProps { + id: number; name: string; weightGrams: number | null; + priceCents: number | null; categoryName: string; + categoryIcon: string; imageFilename: string | null; + productUrl?: string | null; threadId: number; + isActive: boolean; + status: "researching" | "ordered" | "arrived"; + onStatusChange: (status: "researching" | "ordered" | "arrived") => void; + // ADD: pros?: string | null; + // ADD: cons?: string | null; +} +``` + +From src/client/components/CandidateForm.tsx: +```typescript +interface FormData { + name: string; weightGrams: string; priceDollars: string; + categoryId: number; notes: string; productUrl: string; + imageFilename: string | null; + // ADD: pros: string; + // ADD: cons: string; +} +``` + + + + + + + Task 1: Add pros/cons columns through backend + tests + src/db/schema.ts, tests/helpers/db.ts, src/server/services/thread.service.ts, src/shared/schemas.ts, tests/services/thread.service.test.ts + + - createCandidate with pros/cons returns them in the response + - createCandidate without pros/cons returns null for both fields + - updateCandidate can set pros and cons on an existing candidate + - updateCandidate can clear pros/cons by setting to empty string (becomes null via service) + - getThreadWithCandidates includes pros and cons on each candidate object + - All existing thread service tests still pass (no column drift) + + + 1. **Schema** (`src/db/schema.ts`): Add two nullable TEXT columns to `threadCandidates` after `status`: + ```typescript + pros: text("pros"), + cons: text("cons"), + ``` + + 2. **Migration**: Run `bun run db:generate` to produce the ALTER TABLE migration, then `bun run db:push` to apply. + + 3. **Test helper** (`tests/helpers/db.ts`): Add `pros TEXT,` and `cons TEXT,` to the `CREATE TABLE thread_candidates` statement, between the `status` line and the `created_at` line. This is CRITICAL -- without it, in-memory test DBs will silently lack the columns. + + 4. **Service** (`src/server/services/thread.service.ts`): + - `createCandidate`: Add `pros: data.pros ?? null,` and `cons: data.cons ?? null,` to the `.values({})` object. + - `updateCandidate`: Add `pros: string;` and `cons: string;` to the inline `Partial<{...}>` type parameter. + - `getThreadWithCandidates`: Add `pros: threadCandidates.pros,` and `cons: threadCandidates.cons,` to the explicit `.select({})` projection, before the `categoryName` line. + + 5. **Zod schemas** (`src/shared/schemas.ts`): Add to `createCandidateSchema`: + ```typescript + pros: z.string().optional(), + cons: z.string().optional(), + ``` + `updateCandidateSchema` inherits via `.partial()` -- no changes needed there. + + 6. **Tests** (`tests/services/thread.service.test.ts`): Add three test cases: + - In `describe("createCandidate")`: "stores and returns pros and cons" -- create a candidate with `pros: "Lightweight\nGood reviews"` and `cons: "Expensive"`, assert both fields are returned correctly. + - In `describe("updateCandidate")`: "can set and clear pros and cons" -- create a candidate, update with pros/cons values, assert they are set, then update with empty strings, assert they are cleared (returned as empty string or null from DB). + - In `describe("getThreadWithCandidates")`: "includes pros and cons on each candidate" -- create a candidate with pros/cons, fetch via getThreadWithCandidates, assert `candidate.pros` and `candidate.cons` match. + + + cd /home/jlmak/Projects/jlmak/GearBox && bun test tests/services/thread.service.test.ts + + + - pros and cons columns exist in schema.ts and test helper + - Drizzle migration generated and applied + - createCandidate, updateCandidate, getThreadWithCandidates all handle pros/cons + - Zod schemas accept optional pros/cons strings + - All existing + new thread service tests pass + + + + + Task 2: Wire pros/cons through client hooks, form, and card indicator + src/client/hooks/useCandidates.ts, src/client/components/CandidateForm.tsx, src/client/components/CandidateCard.tsx, src/client/routes/threads/$threadId.tsx + + 1. **Hook** (`src/client/hooks/useCandidates.ts`): Add to `CandidateResponse` interface: + ```typescript + pros: string | null; + cons: string | null; + ``` + + 2. **CandidateForm** (`src/client/components/CandidateForm.tsx`): + - Add `pros: string;` and `cons: string;` to `FormData` interface. + - Add `pros: "",` and `cons: "",` to `INITIAL_FORM`. + - In the `useEffect` pre-fill block, add: `pros: candidate.pros ?? "",` and `cons: candidate.cons ?? "",`. + - In `handleSubmit` payload, add: `pros: form.pros.trim() || undefined,` and `cons: form.cons.trim() || undefined,`. + - Add two textarea elements in the form, AFTER the Notes textarea and BEFORE the Product Link input. Each should follow the exact same pattern as the Notes textarea: + - **Pros**: label "Pros", id `candidate-pros`, placeholder "One pro per line...", rows={3} + - **Cons**: label "Cons", id `candidate-cons`, placeholder "One con per line...", rows={3} + - Use identical Tailwind classes as the existing Notes textarea. + + 3. **CandidateCard** (`src/client/components/CandidateCard.tsx`): + - Add `pros?: string | null;` and `cons?: string | null;` to `CandidateCardProps` interface. + - Destructure `pros` and `cons` in the component function parameters. + - Add a visual indicator badge in the `flex flex-wrap gap-1.5` div, after the StatusBadge. When `(pros || cons)` is truthy, render: + ```tsx + {(pros || cons) && ( + + +/- Notes + + )} + ``` + + 4. **Thread detail route** (`src/client/routes/threads/$threadId.tsx`): Pass `pros` and `cons` props to the `` component in the candidates map: + ```tsx + pros={candidate.pros} + cons={candidate.cons} + ``` + + 5. Run `bun run lint` to verify Biome compliance (tabs, double quotes, organized imports). + + + cd /home/jlmak/Projects/jlmak/GearBox && bun test && bun run lint + + + - CandidateResponse includes pros/cons fields + - CandidateForm shows Pros and Cons textareas, pre-fills in edit mode, sends in payload + - CandidateCard shows purple "+/- Notes" badge when pros or cons text exists + - Thread detail page threads pros/cons props to CandidateCard + - Full test suite passes, lint passes + + + + + + +1. `bun test` -- full suite green (existing + new tests) +2. `bun run lint` -- no Biome violations +3. Manual: create a thread, add a candidate with pros and cons text, verify: + - Pros/cons fields appear in the edit form + - Saved text persists after page refresh + - CandidateCard shows the "+/- Notes" indicator badge + - A candidate without pros/cons does NOT show the badge + + + +- RANK-03 fully implemented: pros/cons fields on candidates, editable via form, persisted, with visual indicator +- Zero test regressions +- No column drift between schema.ts and test helper + + + +After completion, create `.planning/phases/10-schema-foundation-pros-cons-fields/10-01-SUMMARY.md` +