diff --git a/.convoys/.metrics.jsonl b/.convoys/.metrics.jsonl index d3091f3..d58efb8 100644 --- a/.convoys/.metrics.jsonl +++ b/.convoys/.metrics.jsonl @@ -75,3 +75,17 @@ {"ts": "2026-06-13T06:20:00Z", "role": "role-architect", "convoy": "scanner-disambiguation-render-test", "repo": "tcg-vault", "skip_flags": [], "classification": "ci", "duration_s": 180, "outcome": "architecture-only"} {"ts": "2026-06-13T06:20:00Z", "role": "role-implementer", "convoy": "scanner-disambiguation-render-test", "repo": "tcg-vault", "skip_flags": [], "classification": "ci", "duration_s": 600, "outcome": "pr-open"} {"ts": "2026-06-14T13:06:36Z", "role": "role-implementer", "convoy": "multi-game-bulk-sync", "repo": "tcg-vault", "skip_flags": [], "brief": 2, "duration_s": 600} +{"ts": "2026-08-14T23:57:39Z", "role": "role-conductor", "convoy": "scanner-mobile-checkout", "repo": "tcg-vault", "skip_flags": [], "classification": "feature", "duration_s": 180, "model": "composer-2.5-fast", "model_tier": "fast"} +{"ts": "2026-08-15T00:14:27Z", "role": "role-ia-architect", "convoy": "scanner-mobile-checkout", "repo": "tcg-vault", "skip_flags": [], "duration_s": 95, "model": "composer-2.5-fast", "model_tier": "fast"} +{"ts": "2026-08-15T00:15:31Z", "role": "role-ui-designer", "convoy": "scanner-mobile-checkout", "repo": "tcg-vault", "skip_flags": [], "duration_s": 90, "model": "composer-2.5-fast", "model_tier": "fast"} +{"ts": "2026-08-15T00:16:36Z", "role": "role-ux-reviewer", "convoy": "scanner-mobile-checkout", "repo": "tcg-vault", "skip_flags": [], "duration_s": 180, "model": "composer-2.5-fast", "model_tier": "fast"} +{"ts": "2026-08-15T00:18:40Z", "role": "role-architect", "convoy": "scanner-mobile-checkout", "repo": "tcg-vault", "skip_flags": [], "duration_s": 420, "model": "composer-2.5", "model_tier": "standard"} +{"ts": "2026-08-15T00:19:56Z", "role": "role-implementer", "convoy": "scanner-mobile-checkout", "repo": "tcg-vault", "skip_flags": [], "brief": 1, "duration_s": 240, "outcome": "complete", "model": "composer-2.5-fast", "model_tier": "fast"} +{"ts": "2026-08-15T00:20:23Z", "role": "role-implementer", "convoy": "scanner-mobile-checkout", "repo": "tcg-vault", "skip_flags": [], "brief": 2, "duration_s": 120, "outcome": "complete", "model": "composer-2.5-fast", "model_tier": "fast"} +{"ts": "2026-08-15T00:22:15Z", "role": "role-implementer", "convoy": "scanner-mobile-checkout", "repo": "tcg-vault", "skip_flags": [], "brief": 3, "duration_s": 600, "outcome": "complete", "model": "composer-2.5-fast", "model_tier": "fast"} +{"ts": "2026-08-15T00:24:15Z", "role": "role-implementer", "convoy": "scanner-mobile-checkout", "repo": "tcg-vault", "skip_flags": [], "brief": 4, "duration_s": 600, "outcome": "complete", "model": "composer-2.5-fast", "model_tier": "fast"} +{"ts": "2026-08-15T00:25:33Z", "role": "role-security-auditor", "convoy": "scanner-mobile-checkout", "repo": "tcg-vault", "skip_flags": [], "duration_s": 480, "model": "gpt-5.6-terra-medium", "model_tier": "security"} +{"ts": "2026-08-15T00:25:54Z", "role": "role-reviewer", "convoy": "scanner-mobile-checkout", "repo": "tcg-vault", "skip_flags": [], "duration_s": 210, "multitask_group": "audit-scanner-mobile-checkout-local", "model": "cursor-grok-4.5-high", "model_tier": "audit"} +{"ts": "2026-08-15T00:26:05Z", "role": "role-a11y-auditor", "convoy": "scanner-mobile-checkout", "repo": "tcg-vault", "skip_flags": [], "duration_s": 78, "model": "cursor-grok-4.5-high", "model_tier": "audit"} +{"ts": "2026-08-15T00:26:08Z", "role": "role-design-system-auditor", "convoy": "scanner-mobile-checkout", "repo": "tcg-vault", "skip_flags": [], "duration_s": 82, "multitask_group": "audit-scanner-mobile-checkout-local", "model": "cursor-grok-4.5-high", "model_tier": "audit"} +{"ts": "2026-08-15T00:56:39Z", "role": "role-implementer", "convoy": "scanner-mobile-checkout", "repo": "tcg-vault", "skip_flags": [], "brief": 4, "duration_s": 240, "outcome": "complete", "model": "composer-2.5-fast", "model_tier": "fast"} diff --git a/.convoys/scanner-mobile-checkout.md b/.convoys/scanner-mobile-checkout.md new file mode 100644 index 0000000..9686245 --- /dev/null +++ b/.convoys/scanner-mobile-checkout.md @@ -0,0 +1,644 @@ +--- +name: scanner-mobile-checkout +classification: feature +success_metric: | + On a phone, opening /scanner goes straight into a full-bleed camera; + each successful identify lands in a local cart (not the database); + the user can open a checkout sheet, select cards, and commit them + to My Collection or a List without leaving the session. +skip: [] +status: open +created: 2026-08-14 +depends_on: + - scanner-rebuild + - redesign-scanner-flow +model_policy: + default_session: auto + roles: + role-conductor: composer-2.5-fast + role-architect: composer-2.5 + role-ia-architect: composer-2.5-fast + role-ux-reviewer: composer-2.5-fast + role-ui-designer: composer-2.5-fast + role-implementer: composer-2.5-fast + role-reviewer: cursor-grok-4.5-high + role-security-auditor: gpt-5.6-terra-medium + role-design-system-auditor: cursor-grok-4.5-high + role-a11y-auditor: cursor-grok-4.5-high + role-doc-writer: auto + escalate_to: claude-sonnet-5-thinking-medium + escalate_to_premium: claude-4.6-opus-high-thinking + never_premium: + - role-reviewer + - role-security-auditor + - role-design-system-auditor + - role-a11y-auditor + - role-ui-designer + - role-doc-writer +design_direction: + source: role-ui-designer + skill: ui-ux-pro-max + skill_version: "2.5.0" + version: 1 + locked_at: 2026-08-14 + product_type: mobile camera scanner checkout cart trading cards glassmorphism + pattern: Immersive camera overlay + bottom-sheet checkout + style: Liquid Glass (repo canonical) + stack: nextjs + layout_reference: image-93781104-69e3-4f0b-b25f-df7c750ef046.png +--- + +# Convoy: scanner-mobile-checkout + +Turn the scanner into a mobile checkout: scan everything first, then +commit the cart. Layout reference is the last attached mock (full-bleed +camera, glass overlays, review as a bottom sheet). + +## Why + +The overnight `scanner-rebuild` got us a three-phase machine (Setup → +Scanning → Review), but the phone experience is still chrome-heavy: +Layout's sidebar, top search bar, and bottom nav compete with the +viewfinder; a destination form blocks the camera; and `handleCardScanned` +writes each match to the database immediately (rebuild D3). That fights +the actual table-scan job — hold the phone, sweep cards, then check out +once. + +Users want supermarket-checkout semantics: open scanner → scan all the +things → open the cart → add selected cards to My Collection or a List +→ leave. No per-card "Add" tap. No setup screen in the way. + +## Scope + +### In scope + +- **Immersive scan chrome (mobile).** Hide Layout sidebar, TopSearchBar, + and MobileNavigation while the camera is live. Camera is full-bleed. + Overlay only: back, title, optional gallery, detection brackets + + status around the tracked card, bottom glass bar (flash, scan status, + Review N). +- **Cart, not auto-route.** Successful identifies enqueue locally. + Nothing POSTs to `user_cards` / collections / decks until checkout. + Reverse rebuild D3 for this flow. +- **Scan peek.** After a match, a compact non-interactive preview slides + up briefly (name + thumbnail + confidence) and auto-dismisses. Tapping + it opens the cart. No condition / foil / Add buttons on the peek. +- **Checkout sheet.** Bottom sheet over the still-live (but paused) + camera. List of cart items with selection, qty, confidence, overflow + menu (remove / condition / foil). Primary: add selected to My + Collection. Secondary: add selected to a List (picker). Copy from + `lib/collection-vocabulary.js` — never "binder" as a button label + (mock says "Save to binder"; product vocab is List). +- **Camera swap.** Front / rear toggle in the bottom bar. Flash stays + rear-only and hides when `torch` is unsupported (iOS Safari). +- **Desktop.** Same cart semantics. At `md+`, cart can be a persistent + side panel instead of a sheet; do not hide the desktop sidebar. + +### Out of scope + +- Identify / OCR / Gemini pipeline (`lib/scanner-card-identify.js`, + `pages/api/scan/identify.js`, OpenCV detection). Layer UI on the + existing hooks. +- Schema / new tables. Cart is client-side session state. +- Deck Mode, game pre-filter, scan-history list on the setup screen + (rebuild Setup). Revisit as a later convoy if needed. +- Estimated total value row from the mock, unless identify payloads + already include `market_price` with no extra fetch. +- Deleting leftover `components/CameraScanner.js` / + `ScannerPageView.js` (pre-rebuild). Separate cleanup. +- Changing desktop visual-diff homepage baseline except as a + side-effect of Layout's new immersive prop. + +## Roles invoked + +1. `role-ia-architect` — scan → peek → cart → commit flow; where + destination choice lives now that Setup is gone. +2. `role-ui-designer` — lock the mobile overlay + checkout sheet against + the last mock, using Liquid Glass tokens (ui-ux-pro-max is installed). +3. `role-ux-reviewer` — cart selection, low-confidence handling, back + with unsaved cart, pause-vs-kill camera under the sheet. +4. `role-architect` — briefs. Likely: (1) Layout immersive + camera + chrome, (2) stop auto-route + cart model, (3) checkout sheet + + destinations. `slice_dependencies` must mark what can run in parallel. +5. `role-implementer` — per brief. +6. Audit fan-out: reviewer + security-auditor + design-system-auditor + + a11y-auditor. + +## Todos + +- [x] IA: route/flow/screen inventory for immersive scan + checkout sheet +- [x] UI Designer: lock overlay recipe (tokens, not hex) from last mock +- [x] UX: cart selection, low-confidence, back-guard, camera pause +- [x] Architect: briefs + whether Layout gets `chrome="immersive"` +- [ ] Stop auto-route in `use-scanner-queue.js`; scans stay `processed: false` until checkout +- [ ] Full-bleed camera; hide app chrome on small viewports +- [ ] Bottom bar: flash, status, Review N; add facingMode swap +- [ ] Non-interactive scan peek; checkout sheet with bulk + per-card commit +- [ ] Pause identification while the sheet is open (`verificationPausedRef`) +- [ ] Tests for cart enqueue (no network) and checkout commit paths +- [ ] Visual-diff: scanner surfaces + Layout immersive; refresh baselines if chrome changes + +## Predecessor + +`scanner-rebuild` (2026-06-13, overnight) shipped the phase machine, +flash hook, toasts, count pill, and review list. This convoy keeps those +hooks and **replaces the UX contract**: skip Setup, immersive camera, +cart-then-commit instead of auto-route. + +## Conductor notes (build shape) + +Likely file ownership for Architect to refine: + +| Area | Files | +| --- | --- | +| Immersive shell | `pages/scanner.js`, `components/Layout.js`, `components/MobileNavigation.js` | +| Camera chrome | `components/scanner/ScannerCamera.js`, `lib/use-camera-scanner.js`, `lib/use-scanner-flash.js` | +| Cart model | `lib/use-scanner-queue.js`, `lib/scanner-session.js` | +| Peek + sheet | new `ScannerScanPeek.js`, rewrite `ScannerReview.js` as a sheet; reuse `ScannerDisambiguation.js` | +| Copy | `lib/collection-vocabulary.js` (`ADD_TO_MY_COLLECTION`, `ADD_TO_LIST`) | + +Do not rewrite identification. `handleCardScanned` should enqueue only. + +## Multitask dispatch + +Planning is serial: IA → UI Designer → UX → Architect. + +After architect: implementer fan-out only if briefs have `depends_on: []` +and disjoint `files:`. Cart-model (stop auto-route) likely blocks the +checkout sheet; immersive Layout may be parallel with cart-model if +they do not both own `pages/scanner.js`. + +After PR draft: `/multitask` audit fan-out +`role-reviewer + role-security-auditor + role-design-system-auditor + role-a11y-auditor` +(group id: `audit-scanner-mobile-checkout-`). + +## IA + +### Affected routes + +- `[modified]` `/scanner` — sole scanner surface. Entry skips Setup and lands on full-bleed camera; cart and checkout live as in-page overlays/sheets on this route (no new URLs). Phase machine collapses from setup → scanning → review into scanning-first with optional cart sheet open/closed. +- `[impacted]` `/login` — unauthenticated `/scanner` visits still redirect here (unchanged gate in `pages/scanner.js`). Post-login return target is TBD (see open questions). +- `[impacted]` `/dashboard`, Layout sidebar, command palette — entry links to `/scanner` unchanged; users now arrive directly in camera instead of Setup. +- `[impacted]` `/my-cards`, `/collections` — not part of the scan flow, but natural post-checkout destinations if the user navigates away after commit. No route or nav changes required. + +No `[new]` routes. Destination choice (My Collection vs List) moves from pre-scan Setup into the checkout sheet on `/scanner` only — **no route changes**. + +### User flow + +```mermaid +flowchart LR + A["Open /scanner"] --> B["Live camera"] + B --> C["Card identified"] + C --> D["Scan peek"] + D --> B + B --> E["Review N"] + E --> F["Checkout sheet"] + F --> G["Add to My Collection / List"] + G --> H["Exit scanner"] +``` + +### Screen inventory + +| Screen | Path | New/modified | Notes | +| --- | --- | --- | --- | +| Scanner — live camera | `/scanner` | modified | Default view on open (mobile: immersive, no Layout chrome). Bottom glass bar: flash, status, camera swap, Review N. Detection brackets + status around tracked card. | +| Scan peek | `/scanner` (overlay) | new | Brief non-interactive slide-up after identify (name, thumbnail, confidence). Auto-dismiss; tap opens checkout sheet. No per-card Add / condition / foil on peek. | +| Checkout cart sheet | `/scanner` (overlay) | modified | Replaces full-page `ScannerReview` as primary cart UI on mobile. Camera stays mounted but paused under sheet. Selection, qty, confidence, overflow (remove / condition / foil). Primary: Add to My Collection; secondary: Add to List (picker). | +| Disambiguation sheet | `/scanner` (overlay) | impacted | Existing `ScannerDisambiguation` stays in overlay stack above camera; blocks identify until resolved, then card enqueues to cart. | +| Scanner Setup | `/scanner` (phase) | impacted | Removed from default entry path (out of scope to delete component). Game filter, deck mode, pre-scan destination, and scan history no longer gate camera start. | +| Auth loading | `/scanner` | impacted | Spinner while `useAuth` resolves; immersive chrome applies once authenticated (see open question on loading shell). | + +Desktop (`md+`): same `/scanner` route; cart may render as persistent side panel instead of sheet; Layout sidebar remains visible. + +### Content / data model deltas + +- **Copy (write / wire):** checkout CTAs from `lib/collection-vocabulary.js` — `ADD_TO_MY_COLLECTION`, `ADD_TO_LIST` / `ADD_TO_LISTS`; bottom-bar "Review N" (N = cart count); flash and camera-swap `aria-label`s; empty-cart and commit-success toasts. Never use "binder" or "Save to binder" as a button label. +- **Copy (retire from entry path):** Setup destination picker, game pre-filter, deck-mode selector, and scan-history list as pre-scan gate copy (component may remain for later convoy). +- **Client session state:** cart is in-memory queue on `/scanner` — cards stay `processed: false` until checkout commit. Reverses rebuild D3 auto-route; no persistence across refresh or navigation away. +- **API calls (commit-time only, unchanged endpoints):** `POST /api/user-cards` (My Collection), `POST /api/collections/:id/cards` (List), existing `GET /api/collections` + `GET /api/decks` for destination lists. Optional `GET` batch-ownership during cart review. No schema / table changes. +- **Immersive Layout:** new chrome mode (likely `chrome="immersive"` on `Layout`) affects how `/scanner` composes global nav — content delta only on this page, not a new route. + +### Open IA questions + +1. **Post-checkout exit:** Product intent says "then exit" — does the user return to the prior page (`router.back()`), land on `/my-cards` or `/collections`, or stay on `/scanner` with an empty cart and live camera? +2. **Post-login deep link:** If an unauthenticated user hits `/scanner`, should login return them to `/scanner` (camera) or a safer default (`/dashboard`)? +3. **Desktop Setup:** Is Setup skipped on `md+` as well, or only on mobile viewports? +4. **Deck destination:** Deck mode and add-to-deck are out of scope — confirm deck is fully absent from checkout destinations (not just hidden behind Setup). +5. **Gallery import:** Convoy mentions "optional gallery" in the top overlay — is this in-scope as a file-picker overlay on `/scanner`, or deferred? +6. **Partial commit:** After adding a subset to My Collection / a List, does the user remain in the sheet with remaining items, or does any successful commit close the session? + +## Decisions (post-IA) + +Locked 2026-08-14 from product intent (parent session). UX / UI / Architect treat these as settled. + +| # | Decision | +| --- | --- | +| D1 | **Stay on camera after commit.** Successful checkout removes committed rows from the cart. If the cart is empty, close the sheet and resume scanning. Back (header) is the only exit from `/scanner`. | +| D2 | **Login returns to `/scanner`.** Keep the existing auth gate; after login, send the user back to the camera, not `/dashboard`. | +| D3 | **Skip Setup on all viewports.** Desktop uses the same scanning-first entry; cart is a side panel at `md+`, not a return of the Setup form. | +| D4 | **No deck destination in checkout.** My Collection and List only. Deck Mode stays out of this convoy. | +| D5 | **Gallery is in-scope.** Top-right control opens a file picker; chosen image goes through the existing identify path and lands in the cart like a live scan. | +| D6 | **Partial commit stays in the sheet.** Uncommitted rows remain. Sheet closes only when the cart is empty or the user dismisses it. | +| D7 | **Persist cart in `sessionStorage`.** Survive refresh within the tab; do not persist across browser sessions. | + +## Design direction + +Locked v1 — 2026-08-14. Layout authority: last mock +(`image-93781104-69e3-4f0b-b25f-df7c750ef046.png`). Generator query: +*"mobile camera scanner checkout cart trading cards glassmorphism"*. + +### Summary + +Deck Hearth's mobile scanner should feel like a **supermarket checkout lane +over a live viewfinder**: full-bleed camera, ember-bracketed detection, +glass overlays that float above card art, a brief non-interactive peek after +each match, and a bottom-sheet cart for bulk commit. Warm ember/flame accents +cast light onto glass — never fill entire panels with brand orange. + +### Pattern + style + +| Field | Value | +| --- | --- | +| Product type | Mobile TCG collection scanner with session cart + checkout | +| App pattern | **Immersive camera overlay + bottom-sheet checkout** (generator's "Bento Grid Showcase" rejected — marketing grid, not camera UX) | +| UI style | **Liquid Glass** — repo canonical (`docs/DESIGN_TOKENS.md`). Generator's "Exaggerated Minimalism" / oversized typography rejected. | +| Stack notes | Next.js Pages router, React 18, Tailwind + CSS variables. Next.js stack query returned 0 rows — follow existing scanner components + `components/ui/` primitives. | +| Desktop (`md+`) | Same cart semantics; cart as persistent side panel; Layout sidebar stays visible. Immersive chrome is mobile-only. | + +### Screen 1 — Live camera (mobile default) + +Full-bleed `