docs: add v1.4 Collection Tools design spec
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,114 @@
|
||||
# v1.4 Collection Tools Design
|
||||
|
||||
**Date:** 2026-04-03
|
||||
**Milestone:** v1.4 Collection Tools
|
||||
**Scope:** Setup impact preview, item quantity, CSV import/export, item duplication
|
||||
|
||||
## Feature 1: Setup Impact Preview
|
||||
|
||||
Already fully designed in `.planning/phases/13-setup-impact-preview/`. Two plans exist:
|
||||
- **13-01**: Pure `computeImpactDeltas` function + `useImpactDeltas` hook + uiStore state (TDD)
|
||||
- **13-02**: `SetupImpactSelector` + `ImpactDeltaBadge` components wired into thread detail
|
||||
|
||||
Execute the existing plans as-is. No design changes needed.
|
||||
|
||||
## Feature 2: Item Quantity
|
||||
|
||||
### Schema
|
||||
|
||||
Add `quantity INTEGER NOT NULL DEFAULT 1` to `items` table via Drizzle migration.
|
||||
|
||||
```ts
|
||||
quantity: integer("quantity").notNull().default(1),
|
||||
```
|
||||
|
||||
### Validation
|
||||
|
||||
Add to `createItemSchema` in `src/shared/schemas.ts`:
|
||||
```ts
|
||||
quantity: z.number().int().positive().optional(),
|
||||
```
|
||||
|
||||
Flows to `updateItemSchema` via `.partial()` automatically.
|
||||
|
||||
### Service Layer
|
||||
|
||||
No special business logic — quantity is a stored field.
|
||||
|
||||
**Totals computation changes:**
|
||||
- `totals.service.ts`: `getCategoryTotals()` and `getGlobalTotals()` must multiply `weightGrams * quantity` and `priceCents * quantity` in their SQL SUM aggregations.
|
||||
- `setup.service.ts`: `getSetupWithItems()` and `getAllSetups()` — when computing setup totals, multiply item weight/price by the item's quantity.
|
||||
|
||||
### UI
|
||||
|
||||
- **ItemForm**: Number input for quantity (min=1), placed below price field. Defaults to 1.
|
||||
- **ItemCard**: Show "x2" badge next to item name when quantity > 1. No badge when quantity is 1.
|
||||
- **Totals**: Already computed server-side with the quantity multiplication. No client-side changes for totals.
|
||||
- **Setup weight/cost**: The item's quantity determines its weight/cost contribution when included in a setup (one `setup_items` row, but totals reflect quantity).
|
||||
|
||||
### Thread Resolution
|
||||
|
||||
When a thread is resolved and a candidate is copied to an item, the new item gets `quantity: 1` (default). No special handling needed.
|
||||
|
||||
## Feature 3: CSV Import/Export
|
||||
|
||||
### Export
|
||||
|
||||
**Endpoint:** `GET /api/items/export`
|
||||
- Returns CSV with headers: `name,quantity,weightGrams,priceCents,category,notes,productUrl`
|
||||
- `Content-Type: text/csv`
|
||||
- `Content-Disposition: attachment; filename="gearbox-export.csv"`
|
||||
- Weight in grams, price in cents (raw values, no formatting)
|
||||
- Category column contains category name (not ID)
|
||||
|
||||
**Service:** `exportItemsCsv(db)` returns a CSV string. Joins items with categories for name lookup.
|
||||
|
||||
### Import
|
||||
|
||||
**Endpoint:** `POST /api/items/import`
|
||||
- Accepts multipart form upload (CSV file)
|
||||
- Parses rows, validates required fields (name is required, others optional)
|
||||
- Category matching: looks up by name (case-insensitive). Creates new category if not found.
|
||||
- Quantity defaults to 1 if not present in CSV
|
||||
- Returns `{ imported: number, created_categories: string[], errors: string[] }`
|
||||
- Skips rows with errors, continues processing remaining rows
|
||||
|
||||
**Service:** `importItemsCsv(db, csvContent: string)` parses and inserts items.
|
||||
|
||||
### UI
|
||||
|
||||
Settings page gets an "Import/Export" section:
|
||||
- "Export CSV" button — triggers download via `GET /api/items/export`
|
||||
- "Import CSV" file input — accepts .csv files, shows count of parsed rows, confirm button to upload
|
||||
- Success/error feedback after import completes
|
||||
|
||||
## Feature 4: Item Duplication
|
||||
|
||||
### API
|
||||
|
||||
**Endpoint:** `POST /api/items/:id/duplicate`
|
||||
- Copies all fields from source item: name, weightGrams, priceCents, categoryId, notes, productUrl, imageFilename, imageSourceUrl, quantity
|
||||
- Appends " (copy)" to the name
|
||||
- New `createdAt`/`updatedAt` timestamps
|
||||
- Returns the new item
|
||||
|
||||
**Service:** `duplicateItem(db, id)` — fetches source item, inserts copy, returns new item.
|
||||
|
||||
### UI
|
||||
|
||||
- Add "Duplicate" action to ItemCard (alongside existing edit/delete actions)
|
||||
- Duplicating opens the edit panel pre-filled with the new item so the user can rename or adjust
|
||||
|
||||
## Phase Ordering
|
||||
|
||||
1. **Item Quantity** — schema change first since CSV import/export and totals depend on it
|
||||
2. **Setup Impact Preview** — execute existing Phase 13 plans
|
||||
3. **Item Duplication** — small, self-contained
|
||||
4. **CSV Import/Export** — depends on quantity field existing in schema
|
||||
|
||||
## Out of Scope
|
||||
|
||||
- Quantity per setup (setup_items.quantity) — items table quantity is sufficient for v1.4
|
||||
- CSV export with formatted weights/prices — raw values are more portable
|
||||
- Image export/import via CSV — images are local files, not CSV-compatible
|
||||
- Bulk edit from CSV preview — import creates, doesn't update existing items
|
||||
Reference in New Issue
Block a user