# 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
