docs(19): capture phase context
This commit is contained in:
@@ -0,0 +1,137 @@
|
||||
# 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*
|
||||
@@ -0,0 +1,79 @@
|
||||
# Phase 19: Reference Item Model & Tags Schema - Discussion Log
|
||||
|
||||
> **Audit trail only.** Do not use as input to planning, research, or execution agents.
|
||||
> Decisions are captured in CONTEXT.md — this log preserves the alternatives considered.
|
||||
|
||||
**Date:** 2026-04-05
|
||||
**Phase:** 19-reference-item-model-tags-schema
|
||||
**Areas discussed:** Reference item structure, Merged data strategy, Tag data model, Migration path
|
||||
**Mode:** Auto (--auto flag) — all areas selected, recommended defaults chosen
|
||||
|
||||
---
|
||||
|
||||
## Reference Item Structure
|
||||
|
||||
| Option | Description | Selected |
|
||||
|--------|-------------|----------|
|
||||
| Add globalItemId to items, remove itemGlobalLinks | Direct FK on items table, drop junction table | ✓ |
|
||||
| Keep itemGlobalLinks alongside globalItemId | Both mechanisms coexist | |
|
||||
| Keep only itemGlobalLinks | No schema change to items | |
|
||||
|
||||
**User's choice:** Add globalItemId to items, remove itemGlobalLinks
|
||||
**Notes:** [auto] 1:1 relationship doesn't need a junction table. Direct FK is simpler. Migration moves existing link data first.
|
||||
|
||||
---
|
||||
|
||||
## Merged Data Strategy
|
||||
|
||||
| Option | Description | Selected |
|
||||
|--------|-------------|----------|
|
||||
| Same Item shape — services merge transparently | Client sees one type, services handle joins | ✓ |
|
||||
| Separate ReferenceItem type | Different response shape for reference items | |
|
||||
| Client-side merge | API returns raw, client resolves | |
|
||||
|
||||
**User's choice:** Same Item shape — services merge transparently
|
||||
**Notes:** [auto] No client-side changes needed for existing views. Service layer joins global data when globalItemId is set.
|
||||
|
||||
---
|
||||
|
||||
## Tag Data Model
|
||||
|
||||
| Option | Description | Selected |
|
||||
|--------|-------------|----------|
|
||||
| Separate tables, flat | tags + global_item_tags tables, no type column | ✓ |
|
||||
| Separate tables, typed | tags + global_item_tags, tags have type column | |
|
||||
| JSON column on globalItems | tags as JSON array, no separate tables | |
|
||||
|
||||
**User's choice:** Separate tables, flat
|
||||
**Notes:** [auto] Proper querying, many-to-many, type categorization can be added later.
|
||||
|
||||
---
|
||||
|
||||
## Migration Path
|
||||
|
||||
| Option | Description | Selected |
|
||||
|--------|-------------|----------|
|
||||
| Migrate links, keep old data as fallback | Set globalItemId from links, don't null out item fields | ✓ |
|
||||
| Migrate links, null out duplicated fields | Clean break, reference items have no local data | |
|
||||
| No migration, only new items use references | Existing items stay as copies forever | |
|
||||
|
||||
**User's choice:** Migrate links, keep old data as fallback
|
||||
**Notes:** [auto] Safe transition — old data remains as fallback while service layer prefers global data.
|
||||
|
||||
---
|
||||
|
||||
## Claude's Discretion
|
||||
|
||||
- Seed tag list content
|
||||
- SQL migration ordering
|
||||
- MCP tool updates timing
|
||||
- Tag filtering logic (AND vs OR)
|
||||
|
||||
## Deferred Ideas
|
||||
|
||||
- Catalog submission system (admin review workflow)
|
||||
- Price intelligence aggregation
|
||||
- Weight intelligence aggregation
|
||||
- Admin tag management UI
|
||||
- Tag type categorization
|
||||
- Personal weight override
|
||||
Reference in New Issue
Block a user