---
phase: 18-global-items-public-profiles
plan: 01
type: execute
wave: 1
depends_on: []
files_modified:
- src/db/schema.ts
- src/shared/schemas.ts
- src/shared/types.ts
- src/db/global-items-seed.json
autonomous: true
requirements: [GLOB-01, GLOB-02, PROF-01, PROF-03]
must_haves:
truths:
- "globalItems table exists with brand, model, category, weightGrams, priceCents, imageUrl, description, createdAt columns"
- "itemGlobalLinks junction table exists linking items to globalItems"
- "users table has displayName, avatarUrl, bio nullable columns"
- "setups table has isPublic boolean column defaulting to false"
- "Zod schemas exist for global item search, item linking, profile update, and setup visibility"
- "Types are inferred from Zod schemas and Drizzle tables, not manually duplicated"
artifacts:
- path: "src/db/schema.ts"
provides: "globalItems, itemGlobalLinks tables + users profile cols + setups isPublic"
contains: "globalItems"
- path: "src/shared/schemas.ts"
provides: "searchGlobalItemsSchema, linkItemSchema, updateProfileSchema"
contains: "searchGlobalItemsSchema"
- path: "src/shared/types.ts"
provides: "GlobalItem, ItemGlobalLink, UpdateProfile, LinkItem types"
contains: "GlobalItem"
- path: "src/db/global-items-seed.json"
provides: "Initial bikepacking gear catalog seed data"
min_lines: 20
key_links:
- from: "src/shared/types.ts"
to: "src/db/schema.ts"
via: "Drizzle $inferSelect"
pattern: "globalItems\\.\\$inferSelect"
- from: "src/shared/types.ts"
to: "src/shared/schemas.ts"
via: "Zod z.infer"
pattern: "z\\.infer.*updateProfileSchema"
---
Define all schema foundations for Phase 18: new database tables (globalItems, itemGlobalLinks), column additions to users (profile fields) and setups (isPublic), Zod validation schemas, TypeScript types, and seed data file.
Purpose: Every subsequent plan depends on these schema definitions. Defining contracts first prevents the scavenger hunt anti-pattern.
Output: Updated schema.ts, schemas.ts, types.ts, and global-items-seed.json
@$HOME/.claude/get-shit-done/workflows/execute-plan.md
@$HOME/.claude/get-shit-done/templates/summary.md
@.planning/PROJECT.md
@.planning/ROADMAP.md
@.planning/STATE.md
@.planning/phases/18-global-items-public-profiles/18-CONTEXT.md
@.planning/phases/18-global-items-public-profiles/18-RESEARCH.md
@src/db/schema.ts
@src/shared/schemas.ts
@src/shared/types.ts
Task 1: Schema tables and column additions
src/db/schema.ts
src/db/schema.ts
Add `boolean` to the drizzle-orm/pg-core imports (per D-01, D-12).
Add the `globalItems` table per D-01:
- `id: serial("id").primaryKey()`
- `brand: text("brand").notNull()`
- `model: text("model").notNull()`
- `category: text("category")`
- `weightGrams: doublePrecision("weight_grams")`
- `priceCents: integer("price_cents")`
- `imageUrl: text("image_url")`
- `description: text("description")`
- `createdAt: timestamp("created_at").defaultNow().notNull()`
Add the `itemGlobalLinks` junction table per D-02:
- `id: serial("id").primaryKey()`
- `itemId: integer("item_id").notNull().references(() => items.id, { onDelete: "cascade" }).unique()` — each user item links to at most one global item
- `globalItemId: integer("global_item_id").notNull().references(() => globalItems.id, { onDelete: "cascade" })`
Extend the `users` table per D-08 — add three nullable text columns:
- `displayName: text("display_name")`
- `avatarUrl: text("avatar_url")`
- `bio: text("bio")`
Extend the `setups` table per D-12 — add:
- `isPublic: boolean("is_public").notNull().default(false)`
Place new tables after setupItems section. Export all new tables.
After schema changes, run `bun run db:generate` to create the migration, then `bun run db:push` to verify it applies cleanly.
bun run db:generate 2>&1 | tail -5
- grep -q "globalItems" src/db/schema.ts
- grep -q "itemGlobalLinks" src/db/schema.ts
- grep -q "displayName" src/db/schema.ts
- grep -q "isPublic" src/db/schema.ts
- grep -q "boolean" src/db/schema.ts
All four schema additions (globalItems table, itemGlobalLinks table, users profile columns, setups isPublic) are defined and exported. Migration generated successfully.
Task 2: Zod schemas, types, and seed data
src/shared/schemas.ts, src/shared/types.ts, src/db/global-items-seed.json
src/shared/schemas.ts, src/shared/types.ts
**schemas.ts** — Add the following Zod schemas at the end of the file:
1. `searchGlobalItemsSchema` per D-04 and D-16:
```
z.object({ q: z.string().optional() })
```
2. `linkItemSchema` per D-18:
```
z.object({ globalItemId: z.number().int().positive() })
```
3. `updateProfileSchema` per D-08, D-21:
```
z.object({
displayName: z.string().max(100).optional(),
avatarUrl: z.string().optional(),
bio: z.string().max(500).optional(),
})
```
4. Update the existing `updateSetupSchema` to include `isPublic` per D-12, D-14:
Add `isPublic: z.boolean().optional()` to the existing schema object.
5. Update the existing `createSetupSchema` if it exists — add `isPublic: z.boolean().optional().default(false)`.
**types.ts** — Add type exports:
- `export type GlobalItem = typeof globalItems.$inferSelect;` (import globalItems, itemGlobalLinks from schema)
- `export type ItemGlobalLink = typeof itemGlobalLinks.$inferSelect;`
- `export type SearchGlobalItems = z.infer;`
- `export type LinkItem = z.infer;`
- `export type UpdateProfile = z.infer;`
**global-items-seed.json** — Create per D-06, D-07. Array of 15-20 bikepacking gear items covering categories like bags (frame bags, handlebar bags, saddle bags), shelters (tents, bivvies, tarps), sleep systems (sleeping bags, pads), cooking, hydration, and lighting. Each object has: `brand`, `model`, `category`, `weightGrams`, `priceCents`, `description`. Use real product names and approximate specs (e.g., Revelate Designs Terrapin, Apidura Expedition Handlebar Pack, Sea to Summit Spark SP1, MSR PocketRocket 2, Nemo Tensor Ultralight). Do NOT include `id` or `createdAt`.
bun run lint 2>&1 | tail -5
- grep -q "searchGlobalItemsSchema" src/shared/schemas.ts
- grep -q "linkItemSchema" src/shared/schemas.ts
- grep -q "updateProfileSchema" src/shared/schemas.ts
- grep -q "GlobalItem" src/shared/types.ts
- grep -q "UpdateProfile" src/shared/types.ts
- test -f src/db/global-items-seed.json
Zod schemas cover global item search, item linking, profile update, and setup visibility. Types inferred from schemas and Drizzle tables. Seed file has 15-20 bikepacking items with real product names.
- `bun run lint` passes with no errors
- `grep -c "export const" src/db/schema.ts` shows new table exports
- `bun run db:generate` creates a clean migration
- Seed JSON is valid: `node -e "JSON.parse(require('fs').readFileSync('src/db/global-items-seed.json','utf8'))"`
Schema.ts has globalItems, itemGlobalLinks, user profile columns, and setup isPublic. Schemas.ts has all new Zod validators. Types.ts exports all new types. Seed JSON file exists with 15-20 items. Lint passes.