Record audit-redesign-scanner-flow-44 rollup and queue follow-up convoys from reviewer, design-system, and a11y fan-out on PRs #42–#44. Co-authored-by: Cursor <cursoragent@cursor.com>
160 lines
5.7 KiB
Markdown
160 lines
5.7 KiB
Markdown
---
|
|
name: redesign-scanner-flow
|
|
classification: feature
|
|
success_metric: |
|
|
Scanner page uses a stack-destination model (one target at top, every scan
|
|
pours into it); per-card condition + foil + quantity captured and propagate
|
|
to destinations; "you already own N" ownership badge via /api/cards/[id]/ownership;
|
|
captured frame persisted to Vercel Blob and attached to user_cards.
|
|
skip: []
|
|
status: closed
|
|
created: 2026-05-27
|
|
depends_on:
|
|
- server-side-scan-pipeline
|
|
---
|
|
|
|
# Convoy: redesign-scanner-flow
|
|
|
|
Redesign the scanner session around a single destination stack, rich card
|
|
metadata, ownership feedback, and persisted scan images.
|
|
|
|
## Why
|
|
|
|
The current scanner treats each card as an isolated add with no session
|
|
context: users re-select destination every scan, cannot set condition/foil/
|
|
quantity in bulk, get no feedback when they already own a card, and lose
|
|
the captured frame after confirm. The audit flagged these as core UX gaps
|
|
that block efficient bulk scanning at the table.
|
|
|
|
## Scope
|
|
|
|
### In scope
|
|
|
|
- **Brief 1 — stack-destination UX + game pre-select**
|
|
- `pages/scanner.js` — session model: one chosen target at top; every
|
|
confirmed scan routes there.
|
|
- `components/ScannerDestinationPicker.js` (new) — collection / deck /
|
|
default collection picker with game filter.
|
|
|
|
- **Brief 2 — condition / foil / quantity + ownership badge**
|
|
- `pages/scanner.js`, `components/ScannedCardItem.js` (extract from
|
|
scanner page).
|
|
- `pages/api/user-cards.js`, `pages/api/decks/[id]/cards.js`,
|
|
`pages/api/collections/[identifier]/cards.js` — accept `condition`,
|
|
`is_foil`, `quantity` body params on POST.
|
|
- Ownership badge via existing `pages/api/cards/[id]/ownership.js`.
|
|
|
|
- **Brief 3 — captured image persistence**
|
|
- `pages/api/scan/upload-image.js` (new) — auth + Blob upload.
|
|
- `components/CameraScanner.js` — upload frame on confirm.
|
|
- `pages/api/user-cards.js` — accept `scan_image_url`.
|
|
|
|
### Out of scope
|
|
|
|
- **OCR / identify pipeline changes** — convoys #2 and #3.
|
|
- **Vocabulary rename** ("My Collection" / "Lists") — convoy
|
|
`rename-collections-vocabulary` (#5).
|
|
- **Schema cleanup** — global `cards.quantity` removal is
|
|
`schema-cleanup-from-scanner-audit`, not here.
|
|
|
|
## Roles invoked
|
|
|
|
1. `role-ia-architect` — destination stack model, nav labels.
|
|
2. `role-ux-reviewer` — bulk-scan flow, ownership badge placement.
|
|
3. `role-architect` — API param contract, 3 briefs.
|
|
4. `role-implementer` — 3 briefs (Briefs 2+3 parallel after Brief 1).
|
|
5. `role-reviewer` + `role-design-system-auditor` + `role-a11y-auditor`.
|
|
|
|
## Todos
|
|
|
|
- [x] IA: stack-destination information architecture
|
|
- [x] UX: condition/foil/quantity controls + ownership badge
|
|
- [x] Architect: brief decomposition + API body-param contract
|
|
- [x] Brief 1 — destination picker + session state (#42)
|
|
- [x] Brief 2 — metadata + ownership (#43)
|
|
- [x] Brief 3 — Blob persistence (#44)
|
|
- [x] Post-PR audit — `audit-redesign-scanner-flow-44` (see `.convoys/redesign-scanner-flow/audit-redesign-scanner-flow-44.md`)
|
|
|
|
## Operator action required
|
|
|
|
**None.** Assumes `BLOB_READ_WRITE_TOKEN` is already provisioned (avatar
|
|
upload path uses Blob today).
|
|
|
|
## Multitask dispatch
|
|
|
|
### Slice dependencies
|
|
|
|
```yaml
|
|
slice_dependencies:
|
|
- brief: 1
|
|
depends_on: []
|
|
files:
|
|
- pages/scanner.js
|
|
- components/ScannerDestinationPicker.js
|
|
- brief: 2
|
|
depends_on: [1]
|
|
files:
|
|
- pages/scanner.js
|
|
- components/ScannedCardItem.js
|
|
- pages/api/user-cards.js
|
|
- pages/api/decks/[id]/cards.js
|
|
- pages/api/collections/[identifier]/cards.js
|
|
- brief: 3
|
|
depends_on: [1]
|
|
files:
|
|
- pages/api/scan/upload-image.js
|
|
- components/CameraScanner.js
|
|
- pages/api/user-cards.js
|
|
```
|
|
|
|
**After Brief 1 merges:** `/multitask role-implementer briefs 2, 3`
|
|
(disjoint file sets except shared `pages/scanner.js` / `user-cards.js` —
|
|
architect must resolve: likely Brief 2 owns `scanner.js` queue UI, Brief 3
|
|
owns CameraScanner + upload route only; adjust `files:` if conflict).
|
|
|
|
Post-PR audit:
|
|
|
|
```
|
|
/multitask role-reviewer + role-design-system-auditor + role-a11y-auditor
|
|
```
|
|
|
|
Group id: `audit-redesign-scanner-flow-<pr>`.
|
|
|
|
**Cross-convoy:** can run **parallel with #3** after #2 Brief 1+2 merge
|
|
(disjoint primary surfaces).
|
|
|
|
## CI impact
|
|
|
|
| Workflow / job | Behavior |
|
|
| --- | --- |
|
|
| `preview-smoke.yml` | Fires. |
|
|
| `visual-diff.yml` | **Fires** — `pages/scanner.js`, `components/**` match paths; `!pages/api/**` still allows page changes through. |
|
|
| New grep gates | None. |
|
|
|
|
## Decisions to ratify (architect)
|
|
|
|
1. **Default destination** — last-used vs explicit pick-required on session start.
|
|
2. **Condition enum** — align with existing `user_cards.condition` VARCHAR values.
|
|
3. **Blob path convention** — e.g. `scans/{userId}/{uuid}.jpg`.
|
|
4. **Brief 2 vs 3 file overlap** — split `pages/scanner.js` ownership to avoid multitask conflict.
|
|
|
|
## Known constraints
|
|
|
|
- **Theme tokens** — no hardcoded hex; use `var(--*)` per ui-and-theming rule.
|
|
- **Rate limits** — upload-image may need `checkUploadRateLimit` if architect
|
|
classifies scan images as upload class (Decision pending).
|
|
- **Mobile scanner** — destination picker must work on narrow viewports.
|
|
|
|
## Acceptance criteria
|
|
|
|
1. User selects one destination; all scans in session target it until changed.
|
|
2. Condition, foil, quantity propagate on add-to-collection/deck/user-cards.
|
|
3. Ownership badge shows when user already holds the card.
|
|
4. Confirmed scan image URL stored on `user_cards` row.
|
|
5. Lint + vitest baseline preserved.
|
|
6. A11y: destination picker keyboard-operable; badge has accessible text.
|
|
|
|
## Out of scope follow-ups
|
|
|
|
- **`god-component-split`** — further split `pages/scanner.js` if still > 500 lines.
|
|
- **`harden-multipart-parser`** — if upload-image uses multipart.
|