From 5558381e09f1bfc99d38e2e7717a0aff4ff93b23 Mon Sep 17 00:00:00 2001 From: Jean-Luc Makiola Date: Sat, 14 Mar 2026 22:27:00 +0100 Subject: [PATCH] docs(01): create phase plan --- .planning/ROADMAP.md | 10 +- .../01-01-PLAN.md | 187 ++++++++++++ .../01-02-PLAN.md | 273 ++++++++++++++++++ .../01-03-PLAN.md | 211 ++++++++++++++ .../01-04-PLAN.md | 168 +++++++++++ 5 files changed, 845 insertions(+), 4 deletions(-) create mode 100644 .planning/phases/01-foundation-and-collection/01-01-PLAN.md create mode 100644 .planning/phases/01-foundation-and-collection/01-02-PLAN.md create mode 100644 .planning/phases/01-foundation-and-collection/01-03-PLAN.md create mode 100644 .planning/phases/01-foundation-and-collection/01-04-PLAN.md diff --git a/.planning/ROADMAP.md b/.planning/ROADMAP.md index 12fb9a5..f7fec3c 100644 --- a/.planning/ROADMAP.md +++ b/.planning/ROADMAP.md @@ -28,11 +28,13 @@ Decimal phases appear between their surrounding integers in numeric order. 3. User can create, rename, and delete categories, and every item belongs to a user-defined category 4. User can see automatic weight and cost totals per category and for the entire collection 5. The app runs as a single Bun process with SQLite storage and serves a clean, minimalist UI -**Plans**: TBD +**Plans:** 4 plans Plans: -- [ ] 01-01: TBD -- [ ] 01-02: TBD +- [ ] 01-01-PLAN.md — Project scaffolding, DB schema, shared schemas, and test infrastructure +- [ ] 01-02-PLAN.md — Backend API: item CRUD, category CRUD, totals, image upload with tests +- [ ] 01-03-PLAN.md — Frontend collection UI: card grid, slide-out panel, category picker, totals bar +- [ ] 01-04-PLAN.md — Onboarding wizard and visual verification checkpoint ### Phase 2: Planning Threads **Goal**: Users can research potential purchases through planning threads — adding candidates, comparing them, and resolving a thread by picking a winner that moves into their collection @@ -71,6 +73,6 @@ Phases execute in numeric order: 1 -> 2 -> 3 | Phase | Plans Complete | Status | Completed | |-------|----------------|--------|-----------| -| 1. Foundation and Collection | 0/0 | Not started | - | +| 1. Foundation and Collection | 0/4 | Planning complete | - | | 2. Planning Threads | 0/0 | Not started | - | | 3. Setups and Dashboard | 0/0 | Not started | - | diff --git a/.planning/phases/01-foundation-and-collection/01-01-PLAN.md b/.planning/phases/01-foundation-and-collection/01-01-PLAN.md new file mode 100644 index 0000000..d39a56f --- /dev/null +++ b/.planning/phases/01-foundation-and-collection/01-01-PLAN.md @@ -0,0 +1,187 @@ +--- +phase: 01-foundation-and-collection +plan: 01 +type: execute +wave: 1 +depends_on: [] +files_modified: + - package.json + - tsconfig.json + - vite.config.ts + - drizzle.config.ts + - index.html + - biome.json + - .gitignore + - src/db/schema.ts + - src/db/index.ts + - src/db/seed.ts + - src/shared/schemas.ts + - src/shared/types.ts + - src/server/index.ts + - src/client/main.tsx + - src/client/routes/__root.tsx + - src/client/routes/index.tsx + - src/client/app.css + - tests/helpers/db.ts +autonomous: true +requirements: + - COLL-01 + - COLL-03 + +must_haves: + truths: + - "Project installs, builds, and runs with bun run dev (both Vite and Hono servers start)" + - "Database schema exists with items and categories tables and proper foreign keys" + - "Shared Zod schemas validate item and category data consistently" + - "Default Uncategorized category is seeded on first run" + - "Test infrastructure runs with in-memory SQLite" + artifacts: + - path: "src/db/schema.ts" + provides: "Drizzle table definitions for items, categories, settings" + contains: "sqliteTable" + - path: "src/db/index.ts" + provides: "Database connection singleton with WAL mode and foreign keys" + contains: "PRAGMA foreign_keys = ON" + - path: "src/db/seed.ts" + provides: "Seeds Uncategorized default category" + contains: "Uncategorized" + - path: "src/shared/schemas.ts" + provides: "Zod validation schemas for items and categories" + exports: ["createItemSchema", "updateItemSchema", "createCategorySchema", "updateCategorySchema"] + - path: "src/shared/types.ts" + provides: "TypeScript types inferred from Zod schemas and Drizzle" + - path: "vite.config.ts" + provides: "Vite config with TanStack Router plugin, React, Tailwind, proxy to Hono" + - path: "tests/helpers/db.ts" + provides: "In-memory SQLite test helper" + key_links: + - from: "src/db/schema.ts" + to: "src/shared/schemas.ts" + via: "Shared field constraints (name required, price as int cents)" + pattern: "priceCents|weightGrams|categoryId" + - from: "vite.config.ts" + to: "src/server/index.ts" + via: "Proxy /api to Hono backend" + pattern: "proxy.*api.*localhost:3000" +--- + + +Scaffold the GearBox project from scratch: install all dependencies, configure Vite + Hono + Tailwind + TanStack Router + Drizzle, create the database schema, shared validation schemas, and test infrastructure. + +Purpose: Establish the complete foundation that all subsequent plans build on. Nothing can be built without the project scaffold, DB schema, and shared types. +Output: A running dev environment with two servers (Vite frontend on 5173, Hono backend on 3000), database with migrations applied, and a test harness ready for service tests. + + + +@/home/jean-luc-makiola/.claude/get-shit-done/workflows/execute-plan.md +@/home/jean-luc-makiola/.claude/get-shit-done/templates/summary.md + + + +@.planning/PROJECT.md +@.planning/ROADMAP.md +@.planning/STATE.md +@.planning/phases/01-foundation-and-collection/01-RESEARCH.md +@.planning/phases/01-foundation-and-collection/01-VALIDATION.md + + + + + + Task 1: Project scaffolding and configuration + package.json, tsconfig.json, vite.config.ts, drizzle.config.ts, index.html, biome.json, .gitignore, src/client/main.tsx, src/client/routes/__root.tsx, src/client/routes/index.tsx, src/client/app.css, src/server/index.ts + + Initialize the project from scratch: + + 1. Run `bun init` in the project root (accept defaults). + 2. Install all dependencies per RESEARCH.md installation commands: + - Core frontend: `bun add react react-dom @tanstack/react-router @tanstack/react-query zustand zod clsx` + - Core backend: `bun add hono @hono/zod-validator drizzle-orm` + - Styling: `bun add tailwindcss @tailwindcss/vite` + - Build tooling: `bun add -d vite @vitejs/plugin-react @tanstack/router-plugin typescript @types/react @types/react-dom` + - DB tooling: `bun add -d drizzle-kit` + - Linting: `bun add -d @biomejs/biome` + - Dev tools: `bun add -d @tanstack/react-query-devtools @tanstack/react-router-devtools` + 3. Initialize Biome: `bunx @biomejs/biome init` + + 4. Create `tsconfig.json` with target ESNext, module ESNext, moduleResolution bundler, jsx react-jsx, strict true, paths "@/*" mapping to "./src/*", types ["bun-types"]. + + 5. Create `vite.config.ts` following RESEARCH.md Pattern 1 exactly. Plugins in order: tanstackRouter (target react, autoCodeSplitting true), react(), tailwindcss(). Proxy /api and /uploads to http://localhost:3000. Build output to dist/client. + + 6. Create `drizzle.config.ts` per RESEARCH.md example (dialect sqlite, schema ./src/db/schema.ts, out ./drizzle, url gearbox.db). + + 7. Create `index.html` as Vite SPA entry point with div#root and script src /src/client/main.tsx. + + 8. Create `src/client/app.css` with Tailwind v4 import: @import "tailwindcss"; + + 9. Create `src/client/main.tsx` with React 19 createRoot, TanStack Router provider, and TanStack Query provider. + + 10. Create `src/client/routes/__root.tsx` as root layout with Outlet. Import app.css here. + + 11. Create `src/client/routes/index.tsx` as default route with placeholder text "GearBox Collection". + + 12. Create `src/server/index.ts` following RESEARCH.md Pattern 1: Hono app, health check at /api/health, static file serving for /uploads/*, production static serving for Vite build, export default { port: 3000, fetch: app.fetch }. + + 13. Add scripts to package.json: "dev:client": "vite", "dev:server": "bun --hot src/server/index.ts", "build": "vite build", "db:generate": "bunx drizzle-kit generate", "db:push": "bunx drizzle-kit push", "test": "bun test", "lint": "bunx @biomejs/biome check ." + + 14. Create `uploads/` directory with a .gitkeep file. Update .gitignore with: gearbox.db, gearbox.db-*, dist/, node_modules/, .tanstack/, uploads/* (but not .gitkeep). + + + bun install && bun run build 2>&1 | tail -5 + + All dependencies installed. bun run build succeeds (Vite compiles frontend). Config files exist and are valid. TanStack Router generates route tree file. + + + + Task 2: Database schema, shared schemas, seed, and test infrastructure + src/db/schema.ts, src/db/index.ts, src/db/seed.ts, src/shared/schemas.ts, src/shared/types.ts, tests/helpers/db.ts + + 1. Create `src/db/schema.ts` following RESEARCH.md Pattern 2 exactly: + - categories table: id (integer PK autoIncrement), name (text notNull unique), emoji (text notNull default box emoji), createdAt (integer timestamp) + - items table: id (integer PK autoIncrement), name (text notNull), weightGrams (real nullable), priceCents (integer nullable), categoryId (integer notNull references categories.id), notes (text nullable), productUrl (text nullable), imageFilename (text nullable), createdAt (integer timestamp), updatedAt (integer timestamp) + - settings table: key (text PK), value (text notNull) for onboarding flag + - Export all tables + + 2. Create `src/db/index.ts` per RESEARCH.md Database Connection Singleton: bun:sqlite Database, PRAGMA journal_mode WAL, PRAGMA foreign_keys ON, export drizzle instance with schema. + + 3. Create `src/db/seed.ts`: seedDefaults() inserts Uncategorized category with box emoji if no categories exist. Export the function. + + 4. Create `src/shared/schemas.ts` per RESEARCH.md Shared Zod Schemas: createItemSchema (name required, weightGrams optional nonneg, priceCents optional int nonneg, categoryId required int positive, notes optional, productUrl optional url-or-empty), updateItemSchema (partial + id), createCategorySchema (name required, emoji with default), updateCategorySchema (id required, name optional, emoji optional). Export all. + + 5. Create `src/shared/types.ts`: Infer TS types from Zod schemas (CreateItem, UpdateItem, CreateCategory, UpdateCategory) and from Drizzle schema (Item, Category using $inferSelect). Export all. + + 6. Create `tests/helpers/db.ts`: createTestDb() function that creates in-memory SQLite, enables foreign keys, applies schema via raw SQL CREATE TABLE statements matching the Drizzle schema, seeds Uncategorized category, returns drizzle instance. This avoids needing migration files for tests. + + 7. Run `bunx drizzle-kit push` to apply schema to gearbox.db. + + 8. Wire seed into src/server/index.ts: import and call seedDefaults() at server startup. + + + bunx drizzle-kit push --force 2>&1 | tail -3 && bun -e "import { db } from './src/db/index.ts'; import { categories } from './src/db/schema.ts'; import './src/db/seed.ts'; const cats = db.select().from(categories).all(); if (cats.length === 0 || cats[0].name !== 'Uncategorized') { throw new Error('Seed failed'); } console.log('OK: seed works, found', cats.length, 'categories');" + + Database schema applied with items, categories, and settings tables. Shared Zod schemas export and validate correctly. Uncategorized category seeded. Test helper creates in-memory DB instances. All types exported from shared/types.ts. + + + + + +- `bun run build` completes without errors +- `bunx drizzle-kit push` applies schema successfully +- Seed script creates Uncategorized category +- `bun -e "import './src/shared/schemas.ts'"` imports without error +- `bun -e "import { createTestDb } from './tests/helpers/db.ts'; const db = createTestDb(); console.log('test db ok');"` succeeds + + + +- All project dependencies installed and lock file committed +- Vite builds the frontend successfully +- Hono server starts and responds to /api/health +- SQLite database has items, categories, and settings tables with correct schema +- Shared Zod schemas validate item and category data +- Test helper creates isolated in-memory databases +- Uncategorized default category is seeded on server start + + + +After completion, create `.planning/phases/01-foundation-and-collection/01-01-SUMMARY.md` + diff --git a/.planning/phases/01-foundation-and-collection/01-02-PLAN.md b/.planning/phases/01-foundation-and-collection/01-02-PLAN.md new file mode 100644 index 0000000..e551fd1 --- /dev/null +++ b/.planning/phases/01-foundation-and-collection/01-02-PLAN.md @@ -0,0 +1,273 @@ +--- +phase: 01-foundation-and-collection +plan: 02 +type: execute +wave: 2 +depends_on: ["01-01"] +files_modified: + - src/server/index.ts + - src/server/routes/items.ts + - src/server/routes/categories.ts + - src/server/routes/totals.ts + - src/server/routes/images.ts + - src/server/services/item.service.ts + - src/server/services/category.service.ts + - tests/services/item.service.test.ts + - tests/services/category.service.test.ts + - tests/services/totals.test.ts + - tests/routes/items.test.ts + - tests/routes/categories.test.ts +autonomous: true +requirements: + - COLL-01 + - COLL-02 + - COLL-03 + - COLL-04 + +must_haves: + truths: + - "POST /api/items creates an item with name, weight, price, category, notes, and product link" + - "PUT /api/items/:id updates any field on an existing item" + - "DELETE /api/items/:id removes an item and cleans up its image file" + - "POST /api/categories creates a category with name and emoji" + - "PUT /api/categories/:id renames a category or changes its emoji" + - "DELETE /api/categories/:id reassigns its items to Uncategorized then deletes the category" + - "GET /api/totals returns per-category and global weight/cost/count aggregates" + - "POST /api/images accepts a file upload and returns the filename" + artifacts: + - path: "src/server/services/item.service.ts" + provides: "Item CRUD business logic" + exports: ["getAllItems", "getItemById", "createItem", "updateItem", "deleteItem"] + - path: "src/server/services/category.service.ts" + provides: "Category CRUD with reassignment logic" + exports: ["getAllCategories", "createCategory", "updateCategory", "deleteCategory"] + - path: "src/server/routes/items.ts" + provides: "Hono routes for /api/items" + - path: "src/server/routes/categories.ts" + provides: "Hono routes for /api/categories" + - path: "src/server/routes/totals.ts" + provides: "Hono route for /api/totals" + - path: "src/server/routes/images.ts" + provides: "Hono route for /api/images upload" + - path: "tests/services/item.service.test.ts" + provides: "Unit tests for item CRUD" + - path: "tests/services/category.service.test.ts" + provides: "Unit tests for category CRUD including reassignment" + - path: "tests/services/totals.test.ts" + provides: "Unit tests for totals aggregation" + key_links: + - from: "src/server/routes/items.ts" + to: "src/server/services/item.service.ts" + via: "Route handlers call service functions" + pattern: "import.*item.service" + - from: "src/server/services/item.service.ts" + to: "src/db/schema.ts" + via: "Drizzle queries against items table" + pattern: "db\\..*from\\(items\\)" + - from: "src/server/services/category.service.ts" + to: "src/db/schema.ts" + via: "Drizzle queries plus reassignment to Uncategorized on delete" + pattern: "update.*items.*categoryId" + - from: "src/server/routes/items.ts" + to: "src/shared/schemas.ts" + via: "Zod validation via @hono/zod-validator" + pattern: "zValidator.*createItemSchema|updateItemSchema" +--- + + +Build the complete backend API: item CRUD, category CRUD with reassignment, computed totals, and image upload. Includes service layer with business logic and comprehensive tests. + +Purpose: Provides the data layer and API endpoints that the frontend will consume. All four COLL requirements are addressed by the API. +Output: Working Hono API routes with validated inputs, service layer, and passing test suite. + + + +@/home/jean-luc-makiola/.claude/get-shit-done/workflows/execute-plan.md +@/home/jean-luc-makiola/.claude/get-shit-done/templates/summary.md + + + +@.planning/PROJECT.md +@.planning/ROADMAP.md +@.planning/STATE.md +@.planning/phases/01-foundation-and-collection/01-RESEARCH.md +@.planning/phases/01-foundation-and-collection/01-VALIDATION.md +@.planning/phases/01-foundation-and-collection/01-01-SUMMARY.md + + + + +From src/db/schema.ts: +```typescript +export const categories = sqliteTable("categories", { + id: integer("id").primaryKey({ autoIncrement: true }), + name: text("name").notNull().unique(), + emoji: text("emoji").notNull().default("..."), + createdAt: integer("created_at", { mode: "timestamp" })... +}); + +export const items = sqliteTable("items", { + id: integer("id").primaryKey({ autoIncrement: true }), + 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"), + createdAt: integer("created_at", { mode: "timestamp" })..., + updatedAt: integer("updated_at", { mode: "timestamp" })..., +}); +``` + +From src/shared/schemas.ts: +```typescript +export const createItemSchema = z.object({ name, weightGrams?, priceCents?, categoryId, notes?, productUrl? }); +export const updateItemSchema = createItemSchema.partial().extend({ id }); +export const createCategorySchema = z.object({ name, emoji? }); +export const updateCategorySchema = z.object({ id, name?, emoji? }); +``` + +From tests/helpers/db.ts: +```typescript +export function createTestDb(): DrizzleInstance; +``` + + + + + + + Task 1: Service layer with tests for items, categories, and totals + src/server/services/item.service.ts, src/server/services/category.service.ts, tests/services/item.service.test.ts, tests/services/category.service.test.ts, tests/services/totals.test.ts + + Item service tests: + - createItem: creates item with all fields, returns item with id and timestamps + - createItem: only name is required, other fields optional + - getAllItems: returns all items with category info joined + - getItemById: returns single item or null + - updateItem: updates specified fields, sets updatedAt + - deleteItem: removes item from DB, returns deleted item (for image cleanup) + - deleteItem: returns null for non-existent id + + Category service tests: + - createCategory: creates with name and emoji + - createCategory: uses default emoji if not provided + - getAllCategories: returns all categories + - updateCategory: renames category + - updateCategory: changes emoji + - deleteCategory: reassigns items to Uncategorized (id=1) then deletes + - deleteCategory: cannot delete Uncategorized (id=1) + + Totals tests: + - getCategoryTotals: returns weight sum, cost sum, item count per category + - getCategoryTotals: excludes empty categories (no items) + - getGlobalTotals: returns overall weight, cost, count + - getGlobalTotals: returns zeros when no items exist + + + Write tests FIRST using createTestDb() from tests/helpers/db.ts. Each test gets a fresh in-memory DB. + + Then implement services: + + 1. `src/server/services/item.service.ts`: + - Functions accept a db instance parameter (for testability) with default to the production db + - getAllItems(): SELECT items JOIN categories, returns items with category name and emoji + - getItemById(id): SELECT single item or null + - createItem(data: CreateItem): INSERT, return with id and timestamps + - updateItem(id, data): UPDATE with updatedAt = new Date(), return updated item + - deleteItem(id): SELECT item first (for image filename), DELETE, return the deleted item data + + 2. `src/server/services/category.service.ts`: + - getAllCategories(): SELECT all, ordered by name + - createCategory(data: CreateCategory): INSERT, return with id + - updateCategory(id, data): UPDATE name and/or emoji + - deleteCategory(id): Guard against deleting id=1. UPDATE all items with this categoryId to categoryId=1, then DELETE the category. Use a transaction. + + 3. Totals functions (can live in item.service.ts or a separate totals module): + - getCategoryTotals(): Per RESEARCH.md Pattern 4 exactly. SELECT with SUM and COUNT, GROUP BY categoryId, JOIN categories. + - getGlobalTotals(): SELECT SUM(weightGrams), SUM(priceCents), COUNT(*) from items. + + + bun test tests/services/ --bail + + All service tests pass. Item CRUD, category CRUD with Uncategorized reassignment, and computed totals all work correctly against in-memory SQLite. + + + + Task 2: Hono API routes with validation, image upload, and integration tests + src/server/routes/items.ts, src/server/routes/categories.ts, src/server/routes/totals.ts, src/server/routes/images.ts, src/server/index.ts, tests/routes/items.test.ts, tests/routes/categories.test.ts + + 1. Create `src/server/routes/items.ts` per RESEARCH.md example: + - GET / returns all items (calls getAllItems service) + - GET /:id returns single item (404 if not found) + - POST / validates body with zValidator("json", createItemSchema), calls createItem, returns 201 + - PUT /:id validates body with zValidator("json", updateItemSchema), calls updateItem, returns 200 or 404 + - DELETE /:id calls deleteItem, cleans up image file if item had imageFilename (try/catch, don't fail delete if file missing), returns 200 or 404 + - Export as itemRoutes + + 2. Create `src/server/routes/categories.ts`: + - GET / returns all categories + - POST / validates with createCategorySchema, returns 201 + - PUT /:id validates with updateCategorySchema, returns 200 or 404 + - DELETE /:id calls deleteCategory, returns 200 or 400 (if trying to delete Uncategorized) or 404 + - Export as categoryRoutes + + 3. Create `src/server/routes/totals.ts`: + - GET / returns { categories: CategoryTotals[], global: GlobalTotals } + - Export as totalRoutes + + 4. Create `src/server/routes/images.ts`: + - POST / accepts multipart/form-data with a single file field "image" + - Validate: file exists, size under 5MB, type is image/jpeg, image/png, or image/webp + - Generate unique filename: `${Date.now()}-${randomUUID()}.${extension}` + - Write to uploads/ directory using Bun.write + - Return 201 with { filename } + - Export as imageRoutes + + 5. Update `src/server/index.ts`: + - Register all routes: app.route("/api/items", itemRoutes), app.route("/api/categories", categoryRoutes), app.route("/api/totals", totalRoutes), app.route("/api/images", imageRoutes) + - Keep health check and static file serving from Plan 01 + + 6. Create integration tests `tests/routes/items.test.ts`: + - Test POST /api/items with valid data returns 201 + - Test POST /api/items with missing name returns 400 (Zod validation) + - Test GET /api/items returns array + - Test PUT /api/items/:id updates fields + - Test DELETE /api/items/:id returns success + + 7. Create integration tests `tests/routes/categories.test.ts`: + - Test POST /api/categories creates category + - Test DELETE /api/categories/:id reassigns items + - Test DELETE /api/categories/1 returns 400 (cannot delete Uncategorized) + + NOTE for integration tests: Use Hono's app.request() method for testing without starting a real server. Create a test app instance with an in-memory DB injected. + + + bun test --bail + + All API routes respond correctly. Validation rejects invalid input with 400. Item CRUD returns proper status codes. Category delete reassigns items. Totals endpoint returns computed aggregates. Image upload stores files. All integration tests pass. + + + + + +- `bun test` passes all service and route tests +- `curl -X POST http://localhost:3000/api/categories -H 'Content-Type: application/json' -d '{"name":"Shelter","emoji":"tent emoji"}'` returns 201 +- `curl -X POST http://localhost:3000/api/items -H 'Content-Type: application/json' -d '{"name":"Tent","categoryId":2}'` returns 201 +- `curl http://localhost:3000/api/totals` returns category and global totals +- `curl -X DELETE http://localhost:3000/api/categories/1` returns 400 (cannot delete Uncategorized) + + + +- All item CRUD operations work via API (create, read, update, delete) +- All category CRUD operations work via API including reassignment on delete +- Totals endpoint returns correct per-category and global aggregates +- Image upload endpoint accepts files and stores them in uploads/ +- Zod validation rejects invalid input with 400 status +- All tests pass with bun test + + + +After completion, create `.planning/phases/01-foundation-and-collection/01-02-SUMMARY.md` + diff --git a/.planning/phases/01-foundation-and-collection/01-03-PLAN.md b/.planning/phases/01-foundation-and-collection/01-03-PLAN.md new file mode 100644 index 0000000..3b4c6df --- /dev/null +++ b/.planning/phases/01-foundation-and-collection/01-03-PLAN.md @@ -0,0 +1,211 @@ +--- +phase: 01-foundation-and-collection +plan: 03 +type: execute +wave: 3 +depends_on: ["01-02"] +files_modified: + - src/client/lib/api.ts + - src/client/lib/formatters.ts + - src/client/hooks/useItems.ts + - src/client/hooks/useCategories.ts + - src/client/hooks/useTotals.ts + - src/client/stores/uiStore.ts + - src/client/components/TotalsBar.tsx + - src/client/components/CategoryHeader.tsx + - src/client/components/ItemCard.tsx + - src/client/components/SlideOutPanel.tsx + - src/client/components/ItemForm.tsx + - src/client/components/CategoryPicker.tsx + - src/client/components/ConfirmDialog.tsx + - src/client/components/ImageUpload.tsx + - src/client/routes/__root.tsx + - src/client/routes/index.tsx +autonomous: true +requirements: + - COLL-01 + - COLL-02 + - COLL-03 + - COLL-04 + +must_haves: + truths: + - "User can see their gear items displayed as cards grouped by category" + - "User can add a new item via the slide-out panel with all fields" + - "User can edit an existing item by clicking its card and modifying fields in the panel" + - "User can delete an item with a confirmation dialog" + - "User can create new categories inline via the category picker combobox" + - "User can rename or delete categories from category headers" + - "User can see per-category weight and cost subtotals in category headers" + - "User can see global totals in a sticky bar at the top" + - "User can upload an image for an item and see it on the card" + artifacts: + - path: "src/client/components/ItemCard.tsx" + provides: "Gear item card with name, weight/price/category chips, and image" + min_lines: 30 + - path: "src/client/components/SlideOutPanel.tsx" + provides: "Right slide-out panel container for add/edit forms" + min_lines: 20 + - path: "src/client/components/ItemForm.tsx" + provides: "Form with all item fields, used inside SlideOutPanel" + min_lines: 50 + - path: "src/client/components/CategoryPicker.tsx" + provides: "Combobox: search existing categories or create new inline" + min_lines: 40 + - path: "src/client/components/TotalsBar.tsx" + provides: "Sticky bar showing total items, weight, and cost" + - path: "src/client/components/CategoryHeader.tsx" + provides: "Category group header with emoji, name, subtotals, and edit/delete actions" + - path: "src/client/routes/index.tsx" + provides: "Collection page assembling all components" + min_lines: 40 + key_links: + - from: "src/client/hooks/useItems.ts" + to: "/api/items" + via: "TanStack Query fetch calls" + pattern: "fetch.*/api/items" + - from: "src/client/components/ItemForm.tsx" + to: "src/client/hooks/useItems.ts" + via: "Mutation hooks for create/update" + pattern: "useCreateItem|useUpdateItem" + - from: "src/client/components/CategoryPicker.tsx" + to: "src/client/hooks/useCategories.ts" + via: "Categories query and create mutation" + pattern: "useCategories|useCreateCategory" + - from: "src/client/routes/index.tsx" + to: "src/client/stores/uiStore.ts" + via: "Panel open/close state" + pattern: "useUIStore" +--- + + +Build the complete frontend collection UI: card grid layout grouped by category, slide-out panel for add/edit with all item fields, category picker combobox, confirmation dialog for delete, image upload, and sticky totals bar. + +Purpose: This is the primary user-facing feature of Phase 1 -- the gear collection view where users catalog, organize, and browse their gear. +Output: A fully functional collection page with CRUD operations, category management, and computed totals. + + + +@/home/jean-luc-makiola/.claude/get-shit-done/workflows/execute-plan.md +@/home/jean-luc-makiola/.claude/get-shit-done/templates/summary.md + + + +@.planning/PROJECT.md +@.planning/ROADMAP.md +@.planning/phases/01-foundation-and-collection/01-CONTEXT.md +@.planning/phases/01-foundation-and-collection/01-RESEARCH.md +@.planning/phases/01-foundation-and-collection/01-01-SUMMARY.md +@.planning/phases/01-foundation-and-collection/01-02-SUMMARY.md + + + +GET /api/items -> Item[] (with category name/emoji joined) +GET /api/items/:id -> Item | 404 +POST /api/items -> Item (201) | validation error (400) +PUT /api/items/:id -> Item (200) | 404 +DELETE /api/items/:id -> { success: true } (200) | 404 + +GET /api/categories -> Category[] +POST /api/categories -> Category (201) | validation error (400) +PUT /api/categories/:id -> Category (200) | 404 +DELETE /api/categories/:id -> { success: true } (200) | 400 (Uncategorized) | 404 + +GET /api/totals -> { categories: CategoryTotals[], global: GlobalTotals } + +POST /api/images -> { filename: string } (201) | 400 + + +From src/shared/types.ts: + Item, Category, CreateItem, UpdateItem, CreateCategory, UpdateCategory + + +From src/client/stores/uiStore.ts: + panelMode: "closed" | "add" | "edit" + editingItemId: number | null + openAddPanel(), openEditPanel(id), closePanel() + + + + + + + Task 1: Data hooks, utilities, UI store, and foundational components + src/client/lib/api.ts, src/client/lib/formatters.ts, src/client/hooks/useItems.ts, src/client/hooks/useCategories.ts, src/client/hooks/useTotals.ts, src/client/stores/uiStore.ts, src/client/components/TotalsBar.tsx, src/client/components/CategoryHeader.tsx, src/client/components/ItemCard.tsx, src/client/components/ConfirmDialog.tsx, src/client/components/ImageUpload.tsx + + 1. Create `src/client/lib/api.ts`: A thin fetch wrapper that throws on non-ok responses with error message from response body. Functions: apiGet(url), apiPost(url, body), apiPut(url, body), apiDelete(url), apiUpload(url, file) for multipart form data. + + 2. Create `src/client/lib/formatters.ts`: formatWeight(grams) returns "123g" or "--" if null. formatPrice(cents) returns "$12.34" or "--" if null. These are display-only, no unit conversion in v1. + + 3. Create `src/client/hooks/useItems.ts` per RESEARCH.md TanStack Query Hook example: useItems() query, useCreateItem() mutation (invalidates items+totals), useUpdateItem() mutation (invalidates items+totals), useDeleteItem() mutation (invalidates items+totals). All mutations invalidate both "items" and "totals" query keys. + + 4. Create `src/client/hooks/useCategories.ts`: useCategories() query, useCreateCategory() mutation (invalidates categories), useUpdateCategory() mutation (invalidates categories), useDeleteCategory() mutation (invalidates categories+items+totals since items may be reassigned). + + 5. Create `src/client/hooks/useTotals.ts`: useTotals() query returning { categories: CategoryTotals[], global: GlobalTotals }. + + 6. Create `src/client/stores/uiStore.ts` per RESEARCH.md Pattern 3: Zustand store with panelMode, editingItemId, openAddPanel, openEditPanel, closePanel. Also add confirmDeleteItemId: number | null with openConfirmDelete(id) and closeConfirmDelete(). + + 7. Create `src/client/components/TotalsBar.tsx`: Sticky bar at top of page (position: sticky, top: 0, z-10). Shows total item count, total weight (formatted), total cost (formatted). Uses useTotals() hook. Clean minimal style per user decision: white background, subtle bottom border, light text. + + 8. Create `src/client/components/CategoryHeader.tsx`: Receives category name, emoji, weight subtotal, cost subtotal, item count. Displays: emoji + name prominently, then subtotals in lighter text. Include edit (rename/emoji) and delete buttons that appear on hover. Delete triggers confirmation. Per user decision: empty categories are NOT shown (filtering happens in parent). + + 9. Create `src/client/components/ItemCard.tsx`: Card displaying item name (prominent), image (if imageFilename exists, use /uploads/{filename} as src with object-fit cover), and tag-style chips for weight, price, and category. Per user decisions: clean, minimal, light and airy aesthetic with white backgrounds and whitespace. Clicking the card calls openEditPanel(item.id). + + 10. Create `src/client/components/ConfirmDialog.tsx`: Modal dialog with "Are you sure you want to delete {itemName}?" message, Cancel and Delete buttons. Delete button is red/destructive. Uses confirmDeleteItemId from uiStore. Calls useDeleteItem mutation on confirm, then closes. + + 11. Create `src/client/components/ImageUpload.tsx`: File input that accepts image/jpeg, image/png, image/webp. On file select, uploads via POST /api/images, returns filename to parent via onChange callback. Shows preview of selected/existing image. Max 5MB validation client-side before upload. + + + bun run build 2>&1 | tail -5 + + All hooks fetch from API and handle mutations with cache invalidation. UI store manages panel and confirm dialog state. TotalsBar, CategoryHeader, ItemCard, ConfirmDialog, and ImageUpload components exist and compile. Build succeeds. + + + + Task 2: Slide-out panel, item form with category picker, and collection page assembly + src/client/components/SlideOutPanel.tsx, src/client/components/ItemForm.tsx, src/client/components/CategoryPicker.tsx, src/client/routes/__root.tsx, src/client/routes/index.tsx + + 1. Create `src/client/components/CategoryPicker.tsx`: Combobox component per user decision. Type to search existing categories, select from filtered dropdown, or create new inline. Uses useCategories() for the list and useCreateCategory() to create new. Props: value (categoryId), onChange(categoryId). Implementation: text input with dropdown list filtered by input text. If no match and input non-empty, show "Create [input]" option. On selecting create, call mutation, wait for result, then call onChange with new category id. Proper ARIA attributes: role combobox, listbox, option. Keyboard navigation: arrow keys to navigate, Enter to select, Escape to close. + + 2. Create `src/client/components/SlideOutPanel.tsx`: Container component that slides in from the right side of the screen. Per user decisions: collection remains visible behind (use fixed positioning with right: 0, width ~400px on desktop, full width on mobile). Tailwind transition-transform + translate-x for animation. Props: isOpen, onClose, title (string). Renders children inside. Backdrop overlay (semi-transparent) that closes panel on click. Close button (X) in header. + + 3. Create `src/client/components/ItemForm.tsx`: Form rendered inside SlideOutPanel. Props: mode ("add" | "edit"), itemId? (for edit mode). When edit mode: fetch item by id (useItems data or separate query), pre-fill all fields. Fields: name (text, required), weight in grams (number input, labeled "Weight (g)"), price in dollars (number input that converts to/from cents for display -- show $, store cents), category (CategoryPicker component), notes (textarea), product link (url input), image (ImageUpload component). On submit: call useCreateItem or useUpdateItem depending on mode, close panel on success. Validation: use Zod createItemSchema for client-side validation, show inline error messages. Per Claude's discretion: all fields visible in a single scrollable form (not tabbed/grouped). + + 4. Update `src/client/routes/__root.tsx`: Import and render TotalsBar at top. Render Outlet below. Render SlideOutPanel (controlled by uiStore panelMode). When panelMode is "add", render ItemForm with mode="add" inside panel. When "edit", render ItemForm with mode="edit" and itemId from uiStore. Render ConfirmDialog. Add a floating "+" button (fixed, bottom-right) to trigger openAddPanel(). + + 5. Update `src/client/routes/index.tsx` as the collection page: Use useItems() to get all items. Use useTotals() to get category totals (for subtotals in headers). Group items by categoryId. For each category that has items (skip empty per user decision): render CategoryHeader with subtotals, then render a responsive card grid of ItemCards (CSS grid: 1 col mobile, 2 col md, 3 col lg). If no items exist at all, show an empty state message encouraging the user to add their first item. Per user decision: card grid layout grouped by category headers. + + + bun run build 2>&1 | tail -5 + + Collection page renders card grid grouped by category. Slide-out panel opens for add/edit with all item fields. Category picker supports search and inline creation. Confirm dialog works for delete. All CRUD operations work end-to-end through the UI. Build succeeds. + + + + + +- `bun run build` succeeds +- Dev server renders collection page at http://localhost:5173 +- Adding an item via the slide-out panel persists to database and appears in the card grid +- Editing an item pre-fills the form and saves changes +- Deleting an item shows confirmation dialog and removes the card +- Creating a new category via the picker adds it to the list +- Category headers show correct subtotals +- Sticky totals bar shows correct global totals +- Image upload displays on the item card + + + +- Card grid layout displays items grouped by category with per-category subtotals +- Slide-out panel works for both add and edit with all item fields +- Category picker supports search, select, and inline creation +- Delete confirmation dialog prevents accidental deletion +- Sticky totals bar shows global item count, weight, and cost +- Empty categories are hidden from the view +- Image upload and display works on cards +- All CRUD operations work end-to-end (UI -> API -> DB -> UI) + + + +After completion, create `.planning/phases/01-foundation-and-collection/01-03-SUMMARY.md` + diff --git a/.planning/phases/01-foundation-and-collection/01-04-PLAN.md b/.planning/phases/01-foundation-and-collection/01-04-PLAN.md new file mode 100644 index 0000000..5049322 --- /dev/null +++ b/.planning/phases/01-foundation-and-collection/01-04-PLAN.md @@ -0,0 +1,168 @@ +--- +phase: 01-foundation-and-collection +plan: 04 +type: execute +wave: 4 +depends_on: ["01-03"] +files_modified: + - src/client/components/OnboardingWizard.tsx + - src/client/stores/uiStore.ts + - src/client/hooks/useSettings.ts + - src/server/routes/settings.ts + - src/server/index.ts + - src/client/routes/__root.tsx +autonomous: false +requirements: + - COLL-01 + - COLL-02 + - COLL-03 + - COLL-04 + +must_haves: + truths: + - "First-time user sees an onboarding wizard guiding them through creating a category and adding an item" + - "After completing onboarding, the wizard does not appear again (persisted to DB)" + - "Returning user goes straight to the collection view" + - "The complete collection experience works end-to-end visually" + artifacts: + - path: "src/client/components/OnboardingWizard.tsx" + provides: "Step-by-step modal overlay for first-run experience" + min_lines: 60 + - path: "src/client/hooks/useSettings.ts" + provides: "TanStack Query hook for settings (onboarding completion flag)" + - path: "src/server/routes/settings.ts" + provides: "API for reading/writing settings" + key_links: + - from: "src/client/components/OnboardingWizard.tsx" + to: "src/client/hooks/useSettings.ts" + via: "Checks and updates onboarding completion" + pattern: "onboardingComplete" + - from: "src/client/hooks/useSettings.ts" + to: "/api/settings" + via: "Fetch and update settings" + pattern: "fetch.*/api/settings" + - from: "src/client/components/OnboardingWizard.tsx" + to: "src/client/hooks/useCategories.ts" + via: "Creates first category during onboarding" + pattern: "useCreateCategory" +--- + + +Build the first-run onboarding wizard and perform visual verification of the complete collection experience. + +Purpose: The onboarding wizard ensures new users are not dropped into an empty page. It guides them through creating their first category and item. The checkpoint verifies the entire Phase 1 UI works correctly. +Output: Onboarding wizard with DB-persisted completion state, and human-verified collection experience. + + + +@/home/jean-luc-makiola/.claude/get-shit-done/workflows/execute-plan.md +@/home/jean-luc-makiola/.claude/get-shit-done/templates/summary.md + + + +@.planning/PROJECT.md +@.planning/ROADMAP.md +@.planning/phases/01-foundation-and-collection/01-CONTEXT.md +@.planning/phases/01-foundation-and-collection/01-03-SUMMARY.md + + + + + + Task 1: Onboarding wizard with settings API and persisted state + src/server/routes/settings.ts, src/server/index.ts, src/client/hooks/useSettings.ts, src/client/components/OnboardingWizard.tsx, src/client/stores/uiStore.ts, src/client/routes/__root.tsx + + 1. Create `src/server/routes/settings.ts`: + - GET /api/settings/:key returns { key, value } or 404 + - PUT /api/settings/:key with body { value } upserts the setting (INSERT OR REPLACE into settings table) + - Export as settingsRoutes + + 2. Update `src/server/index.ts`: Register app.route("/api/settings", settingsRoutes). + + 3. Create `src/client/hooks/useSettings.ts`: + - useSetting(key): TanStack Query hook that fetches GET /api/settings/{key}, returns value or null if 404 + - useUpdateSetting(): mutation that PUTs /api/settings/{key} with { value }, invalidates ["settings", key] + - Specifically export useOnboardingComplete() that wraps useSetting("onboardingComplete") for convenience + + 4. Create `src/client/components/OnboardingWizard.tsx`: Per user decision, a step-by-step modal overlay (not full-page takeover). 3 steps: + - Step 1: Welcome screen. "Welcome to GearBox!" with brief description. "Let's set up your first category." Next button. + - Step 2: Create first category. Show a mini form with category name input and emoji picker (simple: text input for emoji, user pastes/types emoji). Use useCreateCategory mutation. On success, advance to step 3. + - Step 3: Add first item. Show a simplified item form (just name, weight, price, and the just-created category pre-selected). Use useCreateItem mutation. On success, show "You're all set!" and a Done button. + - On Done: call useUpdateSetting to set "onboardingComplete" to "true". Close wizard. + - Modal styling: centered overlay with backdrop blur, white card, clean typography, step indicator (1/3, 2/3, 3/3). + - Allow skipping the wizard entirely with a "Skip" link that still sets onboardingComplete. + + 5. Update `src/client/routes/__root.tsx`: On app load, check useOnboardingComplete(). If value is not "true" (null or missing), render OnboardingWizard as an overlay on top of everything. If "true", render normally. Show a loading state while the setting is being fetched (don't flash the wizard). + + 6. Per RESEARCH.md Pitfall 3: onboarding state is persisted in SQLite settings table, NOT just Zustand. Zustand is only for transient UI state (panel, dialog). The settings table is the source of truth for whether onboarding is complete. + + + bun run build 2>&1 | tail -5 && bun test --bail 2>&1 | tail -5 + + Onboarding wizard renders on first visit (no onboardingComplete setting). Completing it persists the flag. Subsequent visits skip the wizard. Build and tests pass. + + + + Task 2: Visual verification of complete Phase 1 collection experience + Human verifies the complete collection experience works end-to-end: onboarding wizard, card grid, slide-out panel, category management, totals, image upload, and data persistence. + Complete Phase 1 collection experience: card grid grouped by categories, slide-out panel for add/edit items, category picker with inline creation, delete confirmation, sticky totals bar, image upload on cards, and first-run onboarding wizard. + + 1. Delete gearbox.db to simulate first-run: `rm gearbox.db` + 2. Start both dev servers: `bun run dev:server` in one terminal, `bun run dev:client` in another + 3. Visit http://localhost:5173 + + ONBOARDING: + 4. Verify onboarding wizard appears as a modal overlay + 5. Step through: create a category (e.g. "Shelter" with tent emoji), add an item (e.g. "Tent, 1200g, $350") + 6. Complete wizard, verify it closes and collection view shows + + COLLECTION VIEW: + 7. Verify the item appears in a card with name, weight chip, price chip + 8. Verify the category header shows "Shelter" with emoji and subtotals + 9. Verify the sticky totals bar at top shows 1 item, 1200g, $350.00 + + ADD/EDIT: + 10. Click the "+" button, verify slide-out panel opens from right + 11. Add another item in a new category, verify both categories appear with correct subtotals + 12. Click an existing card, verify panel opens with pre-filled data for editing + 13. Edit the weight, save, verify totals update + + CATEGORY MANAGEMENT: + 14. Hover over a category header, verify edit/delete buttons appear + 15. Delete a category, verify items reassign to Uncategorized + + DELETE: + 16. Click delete on an item, verify confirmation dialog appears + 17. Confirm delete, verify item removed and totals update + + IMAGE: + 18. Edit an item, upload an image, verify it appears on the card + + PERSISTENCE: + 19. Refresh the page, verify all data persists and onboarding wizard does NOT reappear + + Type "approved" if the collection experience works correctly, or describe any issues found. + + + + + +- Onboarding wizard appears on first run, not on subsequent visits +- All CRUD operations work through the UI +- Category management (create, rename, delete with reassignment) works +- Totals are accurate and update in real-time after mutations +- Cards display clean, minimal aesthetic per user decisions +- Image upload and display works + + + +- First-time users see onboarding wizard that guides through first category and item +- Onboarding completion persists across page refreshes (stored in SQLite settings table) +- Full collection CRUD works end-to-end through the UI +- Visual design matches user decisions: clean, minimal, light and airy, card grid with chips +- Human approves the complete collection experience + + + +After completion, create `.planning/phases/01-foundation-and-collection/01-04-SUMMARY.md` +