# Phase 19: Reference Item Model & Tags Schema - Context

**Gathered:** 2026-04-05
**Status:** Ready for planning

<domain>
## Phase Boundary

Transform collection items from full data copies to reference pointers at global catalog entries. A reference item stores `globalItemId` + personal fields (categoryId, notes, purchasePriceCents, imageFilename, quantity); base data (brand, model, weight, MSRP, image, description) comes from the linked global item. Add a tag system so global items can be tagged for discovery and filtering. Update thread candidates to support `globalItemId`. Update thread resolution to create reference items with auto-link.

</domain>

<decisions>
## Implementation Decisions

### Reference Item Model
- **D-01:** Add `globalItemId` (nullable FK → globalItems) directly to the `items` table. When set, the item is a "reference item" — base data comes from the global item.
- **D-02:** Remove the `itemGlobalLinks` junction table entirely. It was a 1:1 relationship — a direct FK on items is simpler. Migrate existing link data first.
- **D-03:** Personal fields on items remain: `categoryId`, `notes`, `imageFilename`, `imageSourceUrl`, `quantity`. Add `purchasePriceCents` (nullable integer) for what the user actually paid.
- **D-04:** For reference items, `name`, `weightGrams`, `priceCents` on the items row are kept NULL or empty. The service layer merges global data when returning items.
- **D-05:** Standalone items (no `globalItemId`) continue to work as before — fully self-contained with all fields populated. This is the "manual entry" path.

### Merged Data Strategy
- **D-06:** Service layer handles merging transparently. `getAllItems()` and `getItem()` join on globalItems when `globalItemId` is set, returning a unified shape. Clients see one type — no need to distinguish reference vs standalone.
- **D-07:** API response shape stays the same as current Item type. Global data fills in name, weight, price, etc. for reference items. Personal overrides (if any exist) take precedence — but initially only the fields listed in D-03 are personal.
- **D-08:** `productUrl` on reference items: comes from the global item's future `productUrl` field if added, otherwise NULL. For now, reference items may not have a product URL unless the global item has one. (Not critical — users can add to notes.)

### Thread Candidates
- **D-09:** Add `globalItemId` (nullable FK → globalItems) to `threadCandidates` table. Candidates added from catalog have this set.
- **D-10:** Candidates with `globalItemId` display global item data (brand + model as name, weight, price, image). Personal candidate fields (notes, pros, cons, status) remain on the candidate row.
- **D-11:** Candidates without `globalItemId` work as before (fully manual data).

### Thread Resolution
- **D-12:** When resolving a thread where the winning candidate has `globalItemId`, create a reference item: set `items.globalItemId` = candidate's `globalItemId`, copy only personal fields (categoryId, notes, imageFilename). Don't copy name/weight/price — those come from global item.
- **D-13:** When resolving a candidate WITHOUT `globalItemId`, behavior stays the same as today — full data copy.

### Tag System
- **D-14:** New `tags` table: `id` (serial), `name` (text, unique, not null), `createdAt` (timestamp).
- **D-15:** New `globalItemTags` join table: `globalItemId` (FK), `tagId` (FK), composite primary key. Many-to-many.
- **D-16:** Tags are flat — no type column (gear-type, activity, property, etc.) for now. Keep it simple. Type categorization can be added later.
- **D-17:** Seed initial tag set via script covering common bikepacking/outdoor gear categories: handlebar-bag, framebag, saddlebag, tent, bivy, tarp, sleeping-bag, sleeping-pad, stove, cookware, headlamp, waterproof, ultralight, budget, premium, etc.
- **D-18:** Global item search extends to support tag filtering: `GET /api/global-items?q=...&tags=handlebar-bag,waterproof` returns items matching ALL specified tags.

### Migration
- **D-19:** Migration script: for each row in `itemGlobalLinks`, set `items.globalItemId = itemGlobalLinks.globalItemId`, then drop `itemGlobalLinks` table.
- **D-20:** Existing items that had links keep their current name/weight/price data populated (not nulled out) — the service layer prefers global data for reference items, but having the old data as fallback is safe during transition.

### Claude's Discretion
- Exact seed tag list content and count
- SQL migration ordering (add columns, migrate data, drop old table)
- Whether to update MCP tools in this phase or defer
- Test helper updates for new schema
- Whether global item search uses AND or OR for multiple tags (recommendation: AND — intersection filtering)

</decisions>

<canonical_refs>
## Canonical References

**Downstream agents MUST read these before planning or implementing.**

### Design Spec
- `docs/superpowers/specs/2026-04-05-catalog-driven-gear-flow-design.md` — Full catalog-driven gear flow vision. Phase 19 implements the data model foundation described here.

### Schema
- `src/db/schema.ts` — Current schema. Add globalItemId to items and threadCandidates, add tags + globalItemTags tables, remove itemGlobalLinks.

### Services (must be updated for merged data)
- `src/server/services/item.service.ts` — Item CRUD — must join globalItems for reference items
- `src/server/services/global-item.service.ts` — Global item search — add tag filtering, remove linkItemToGlobal/unlinkItemFromGlobal (replaced by direct FK)
- `src/server/services/thread.service.ts` — resolveThread() at line 293 — must create reference items when candidate has globalItemId

### Routes
- `src/server/routes/items.ts` — Item routes — link/unlink endpoints removed (replaced by globalItemId on item)
- `src/server/routes/global-items.ts` — Add tag filtering to search

### Client (linking UI removed)
- `src/client/components/LinkToGlobalItem.tsx` — Remove or repurpose (direct linking replaced by add-from-catalog flow in Phase 21)

### Tests
- `tests/services/global-item.service.test.ts` — Update for removed link/unlink, add tag tests
- `tests/services/thread.service.test.ts` — Update resolve tests for reference item creation
- `tests/helpers/db.ts` — Update for new schema (tags, globalItemTags, items.globalItemId)

### Requirements
- `.planning/REQUIREMENTS.md` — CATFLOW-03, CATFLOW-04, CATFLOW-05, CATFLOW-06, TAG-01, TAG-02

</canonical_refs>

<code_context>
## Existing Code Insights

### Reusable Assets
- `globalItems` table already exists with brand, model, category, weight, price, image, description
- `global-item.service.ts` has searchGlobalItems() with ILIKE — extend with tag filtering
- `thread.service.ts` resolveThread() — modify step 4 (item creation) for reference items
- `seed-global-items.ts` — extend seed script to also seed tags and assign them

### Established Patterns
- Service DI pattern: `(db: Db, userId: number, ...)` — consistent across all services
- Drizzle ORM with pg-core — use same patterns for new tables
- Zod schemas in `src/shared/schemas.ts` — update for new fields
- Types inferred from Zod + Drizzle in `src/shared/types.ts`

### Integration Points
- `src/db/schema.ts` — Add tables, add columns, remove itemGlobalLinks
- `src/server/index.ts` — No new route groups needed (existing global-items routes extended)
- `tests/helpers/db.ts` — Must include new tables in test migrations
- `src/db/seed-global-items.ts` — Extend to seed tags

</code_context>

<specifics>
## Specific Ideas

- The reference model is the foundation for future crowd-sourced data: purchase prices (what users actually paid), real-world weights (vs manufacturer claims), and geographic price intelligence. The `purchasePriceCents` field is the first step.
- Catalog submission system (manual item → submit → admin review → convert to reference) is deferred but the data model should make this possible later.
- Weight override on personal items is deferred — not critical for now.

</specifics>

<deferred>
## Deferred Ideas

- Catalog submission system — manual items submitted for admin review, approved items convert to references (saved in memory)
- Crowd-sourced purchase price intelligence — aggregate what users paid, show market prices vs MSRP (saved in memory)
- Crowd-sourced weight intelligence — user-submitted actual weights vs manufacturer claims (saved in memory)
- Admin tag management UI — manage tags via settings, not just seed script
- Tag type categorization — gear-type, activity, property, mounting as tag types
- Personal weight override field on items

</deferred>

---

*Phase: 19-reference-item-model-tags-schema*
*Context gathered: 2026-04-05*
