4.4 KiB
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
computeImpactDeltasfunction +useImpactDeltashook + uiStore state (TDD) - 13-02:
SetupImpactSelector+ImpactDeltaBadgecomponents 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.
quantity: integer("quantity").notNull().default(1),
Validation
Add to createItemSchema in src/shared/schemas.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()andgetGlobalTotals()must multiplyweightGrams * quantityandpriceCents * quantityin their SQL SUM aggregations.setup.service.ts:getSetupWithItems()andgetAllSetups()— 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_itemsrow, 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/csvContent-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/updatedAttimestamps - 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
- Item Quantity — schema change first since CSV import/export and totals depend on it
- Setup Impact Preview — execute existing Phase 13 plans
- Item Duplication — small, self-contained
- 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