--- phase: quick plan: 260406-j44 type: execute wave: 1 depends_on: [] files_modified: - src/db/dev-seed.ts - src/db/dev-seed-data.ts - package.json autonomous: true requirements: [] must_haves: truths: - "Running `bun run db:seed:dev` populates the database with realistic bikepacking gear data" - "Re-running the script on a populated DB is safe (idempotent — skips if data exists)" - "All foreign key relationships are valid (items reference real categories, setup_items reference real items, etc.)" artifacts: - path: "src/db/dev-seed-data.ts" provides: "All seed data as typed constants" - path: "src/db/dev-seed.ts" provides: "Idempotent seed runner" - path: "package.json" provides: "db:seed:dev script entry" key_links: - from: "src/db/dev-seed.ts" to: "src/db/schema.ts" via: "drizzle insert operations" pattern: "db\\.insert\\(schema\\." - from: "src/db/dev-seed.ts" to: "src/db/dev-seed-data.ts" via: "import data constants" pattern: "import.*from.*dev-seed-data" --- Create a comprehensive development seed script that populates PostgreSQL with realistic bikepacking/outdoor gear data for local development. Purpose: Enable developers to quickly stand up a fully-populated dev environment with realistic data spanning all entity types — global items, tags, user collections, threads, setups, and settings. Output: Two new files (`dev-seed-data.ts` for data, `dev-seed.ts` for the runner) and a `db:seed:dev` npm script. @$HOME/.claude/get-shit-done/workflows/execute-plan.md @$HOME/.claude/get-shit-done/templates/summary.md @CLAUDE.md @src/db/schema.ts @src/db/seed-global-items.ts @src/db/global-items-seed.json @tests/helpers/db.ts @src/db/index.ts From src/db/schema.ts: - users: { id (serial PK), logtoSub (text, unique), displayName (text), avatarUrl (text), bio (text) } - categories: { id (serial PK), name (text), icon (text), userId (int FK users) } — unique(userId, name) - items: { id (serial PK), name, weightGrams, priceCents, categoryId (FK), userId (FK), notes, productUrl, imageFilename, imageSourceUrl, quantity (default 1), globalItemId (FK nullable), purchasePriceCents } - threads: { id (serial PK), name, status (default "active"), resolvedCandidateId, categoryId (FK), userId (FK) } - threadCandidates: { id (serial PK), threadId (FK cascade), name, weightGrams, priceCents, categoryId (FK), notes, productUrl, imageFilename, imageSourceUrl, status (default "researching"), pros, cons, sortOrder, globalItemId (FK nullable) } - setups: { id (serial PK), name, userId (FK), isPublic (default false) } - setupItems: { id (serial PK), setupId (FK cascade), itemId (FK cascade), classification (default "base") } - globalItems: { id (serial PK), brand, model, category, weightGrams, priceCents, imageUrl, description } - tags: { id (serial PK), name (unique) } - globalItemTags: { globalItemId (FK cascade), tagId (FK cascade) } — composite PK - settings: { userId (FK), key (text), value (text) } — composite PK(userId, key) From src/db/index.ts: - db: drizzle instance connected to PostgreSQL via DATABASE_URL From src/db/seed-global-items.ts (idempotent pattern to follow): - Check `existing.length > 0` before inserting - Accept optional `db` parameter with production db as default Task 1: Create seed data constants src/db/dev-seed-data.ts Create `src/db/dev-seed-data.ts` exporting typed constant arrays for all seed data. Keep data and logic separate for maintainability. **Categories** (8-10 with icons): - Bags (briefcase), Shelter (tent), Sleep System (moon), Cooking (flame), Lighting (flashlight), Tools & Repair (wrench), Clothing (shirt), Water (droplets), Electronics (battery), Navigation (compass) **Global Items** (35-45 items spanning categories). Use real bikepacking gear brands/models with realistic weights and prices. Examples per category: - Bags: Revelate Designs Terrapin, Apidura Expedition Handlebar Pack, Ortlieb Frame-Pack, Rockgeist BarJam, Oveja Negra Superwedgie - Shelter: Zpacks Duplex, Tarptent Stratospire, Durston X-Mid 1, Big Agnes Copper Spur HV UL1 - Sleep: Enlightened Equipment Enigma 20, Therm-a-Rest NeoAir XLite, Nemo Tensor Insulated, Sea to Summit Aeros Pillow - Cooking: BRS-3000T, Soto Windmaster, Toaks 750ml, Snow Peak Ti-Mini Solo - Lighting: Nitecore NU25, Lezyne Lite Drive, Fenix HL60R - Tools: Park Tool IB-3, Lezyne CNC Chain Breaker, Gorilla Tape mini roll - Clothing: Patagonia R1 Air, Frogg Toggs UL2 Rain Suit, Buff Merino Wool - Water: Sawyer Squeeze, Katadyn BeFree, HydraPak Seeker 2L - Electronics: Anker 10000 PD, Garmin inReach Mini 2 - Navigation: Wahoo ELEMNT BOLT, Caltopo printed maps holder Each global item: `{ brand, model, category, weightGrams, priceCents, description }` — use integers for priceCents (e.g., 67900 for $679.00). **Tags** (reuse existing SEED_TAGS from seed-global-items.ts plus a few more): - Existing tags are already seeded by `seedTags()`, so do NOT re-seed them. **Tag assignments** — array of `{ globalItemIndex, tagNames }` mapping global items to tags by index position and tag name. Example: index 0 (Terrapin) gets ["saddlebag", "waterproof", "bikepacking"]. Assign 2-4 tags per global item. **User items** (15-20 items for the dev user's collection): - ~10 items linked to global items via `globalItemId` (reference items). Use the index into the global items array. Include `purchasePriceCents` for some (actual price paid, sometimes different from MSRP). - ~5-7 standalone items with no globalItemId (custom/unique gear the user added manually) - Spread across categories. Include notes on some, quantity > 1 on a couple. **Threads** (3 threads): 1. Active thread "Handlebar Bag Upgrade" in Bags category with 3 candidates (some catalog-linked via globalItemId index), statuses: researching/shortlisted/researching 2. Active thread "Navigation Computer" in Electronics with 2 candidates 3. Resolved thread "Camp Stove" in Cooking with 2 candidates, one marked "arrived" **Setups** (2 setups): 1. "Weekend Overnighter" — 6-8 items from the user's collection, mix of base/worn/consumable classifications, isPublic: true 2. "Ultra-Light Day Ride" — 3-4 items, isPublic: false **Settings**: weightUnit "g", currency "EUR" Export everything as named constants with proper TypeScript types (using `as const` where appropriate for literal inference). bunx tsc --noEmit src/db/dev-seed-data.ts 2>&1 | head -20 All seed data constants exported with proper types, no TypeScript errors Task 2: Create seed runner and wire npm script src/db/dev-seed.ts, package.json Create `src/db/dev-seed.ts` — the idempotent seed runner. **Structure:** 1. Import `db` from `./index.ts`, all schema tables from `./schema.ts`, all data from `./dev-seed-data.ts`, and `seedGlobalItems` from `./seed-global-items.ts` 2. Main `seedDevData()` async function: **Idempotency check:** Query `users` table for a user with `logtoSub = "dev-user-seed"`. If found, log "Dev seed data already exists, skipping." and return early. This single check gates the entire script since all other data depends on this user. **Insertion order** (respects FK constraints): 1. Call `seedGlobalItems(db)` first — reuse existing function to populate global_items and tags 2. Insert dev user: `{ logtoSub: "dev-user-seed", displayName: "Dev User", bio: "Bikepacking enthusiast" }` — capture returned user for userId FK 3. Insert categories (with userId from step 2) — capture returned rows for categoryId mapping 4. Look up tag IDs by name from tags table (they were seeded in step 1) — build `tagNameToId` map 5. Insert global item tag assignments using `globalItemTags` table — use the global item IDs from step 1 and tag IDs from step 4. Note: `seedGlobalItems` does not return IDs, so query `globalItems` table to get IDs by brand+model matching after seeding. 6. Insert user items (with userId, categoryId, globalItemId references) — capture returned rows for setup_items 7. Insert threads (with userId, categoryId) — capture returned IDs 8. Insert thread candidates (with threadId, categoryId, globalItemId) — for the resolved thread, set `resolvedCandidateId` on the thread after inserting candidates 9. Insert setups (with userId) — capture returned IDs 10. Insert setup_items (with setupId, itemId references from step 6) 11. Insert settings (with userId) 12. Log summary: "Dev seed complete: X global items, X tags, X user items, X threads, X setups" **Script entry point:** At bottom of file: ```typescript seedDevData() .then(() => process.exit(0)) .catch((err) => { console.error("Seed failed:", err); process.exit(1); }); ``` **package.json:** Add `"db:seed:dev": "bun run src/db/dev-seed.ts"` to scripts section. Use `db.insert(...).values(...).returning()` to capture IDs needed for FK references. Use `db.insert(...).values(...)` (no returning) for leaf tables. Wrap everything in a try/catch. On error, log the error and re-throw. Do NOT use transactions (PostgreSQL serial IDs and the idempotency check make this safe enough for dev seeding). bunx tsc --noEmit src/db/dev-seed.ts 2>&1 | head -20 - `bun run db:seed:dev` executes without error on a fresh (migrated) database - Re-running logs "already exists, skipping" and exits cleanly - Database contains: 1 dev user, 8+ categories, 35+ global items with tag assignments, 15+ user items (some catalog-linked), 3 threads with candidates, 2 setups with items, settings 1. `bunx tsc --noEmit src/db/dev-seed-data.ts src/db/dev-seed.ts` — no type errors 2. `bun run db:seed:dev` — completes without error (requires running PostgreSQL) 3. Re-run `bun run db:seed:dev` — logs skip message, exits 0 - Script populates all entity types: users, categories, global items, tags, global item tag links, items (reference + standalone), threads, candidates (some catalog-linked), setups, setup_items, settings - Idempotent: safe to run multiple times - Data is realistic bikepacking gear with accurate weights/prices - Foreign key relationships are all valid - `bun run db:seed:dev` is the only command needed After completion, create `.planning/quick/260406-j44-comprehensive-dev-seed-script-for-bikepa/260406-j44-SUMMARY.md`