# 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