deckhearth/.convoys/redesign-scanner-flow.md
Randall Stillwell ff73743a9f docs(convoy): close redesign-scanner-flow post-PR audit
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>
2026-05-27 14:09:50 -05:00

5.7 KiB

name classification success_metric skip status created depends_on
redesign-scanner-flow feature 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.
closed 2026-05-27
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

  • IA: stack-destination information architecture
  • UX: condition/foil/quantity controls + ownership badge
  • Architect: brief decomposition + API body-param contract
  • Brief 1 — destination picker + session state (#42)
  • Brief 2 — metadata + ownership (#43)
  • Brief 3 — Blob persistence (#44)
  • 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

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 Firespages/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.