--- name: scanner-desktop-layout classification: feature success_metric: | On md+ viewports, /scanner keeps the desktop app chrome (sidebar + top bar), shows a framed camera workstation with a real webcam device picker, Upload Image, Batch Scan, Auto-detect, Scanner Tips, a live match inspector, and a bottom strip with Recent Scans / Scan Queue / Duplicates — without regressing the mobile immersive checkout. skip: [] status: shipped created: 2026-08-15 depends_on: - scanner-mobile-checkout - scanner-rebuild 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-15 product_type: desktop trading card scanner workstation pattern: Feature-Rich Showcase (workstation variant) style: Liquid Glass / glassmorphism stack: nextjs layout_reference: image-1eeebe23-9983-4a96-a3e7-4e3cdfdceb5b.png --- # Convoy: scanner-desktop-layout Give `/scanner` a dedicated desktop workstation layout from the attached dark/light mock (camera + live result + history strip), while leaving the shipped mobile immersive checkout alone. Worktree: `tcg-vault-worktrees/scanner-desktop-layout` on `convoy/scanner-desktop-layout` (branched from `origin/main` @ `0d52858`). Layout reference: `image-1eeebe23-9983-4a96-a3e7-4e3cdfdceb5b.png`. Do **not** land this on `dashboard-home-realignment` — that convoy owns sidebar IA + top-bar chrome. Do **not** land this on `scanner-identify-upgrade` — that epic owns detect/identify accuracy. ## Why `scanner-mobile-checkout` shipped the phone job: full-bleed camera, local cart, checkout sheet. Desktop (`md+`) got the leftover composition — the same camera chrome plus a 360px cart side panel (`ScannerReview` `variant="side-panel"`). That is not a desk workstation. On a laptop the user wants to see the webcam, inspect the current match (set, rarity, number, condition, foil, confidence), decide what to do with it, and keep a history/queue in view — without losing the app sidebar or search bar. The mock is that layout. Today they get a phone overlay stretched into a column. ## Scope ### In scope - **Desktop-only composition (`md+`).** Keep Layout sidebar + TopSearchBar. Stop treating desktop as an immersive camera page with a bolted-on cart. Mobile (`max-md`) stays `chrome="immersive"` + checkout sheet. - **Framed camera viewport.** Large live feed with ember corner brackets (already in `ScannerCamera`), Auto-detect status, and desk controls under the frame. Camera is a panel in the page, not a full-bleed overlay. - **Real webcam device picker.** `enumerateDevices` + `deviceId` in `useCameraScanner` (not facing-mode swap relabeled). Persist the last-used device for the tab if cheap. Empty-list / denied- permission fallback. Mobile keeps the existing facing-mode toggle — do not replace the phone chrome with a device ``, Batch Scan progress, and the three-tab strip. Do **not** skip `ui-design`. 3. `role-ux-reviewer` — inspect-then-add vs scan-all-then-checkout on desktop; Auto-detect off; Rescan; empty inspector; leave with an uncommitted queue; sequential batch cancel; duplicate tab actions (increment qty vs skip vs still add). 4. `role-architect` — briefs. Likely: (1) page composition + Layout chrome split, (2) camera panel + `deviceId` picker, (3) result inspector, (4) history/queue/duplicates strip, (5) batch multi-file identify + Tips. `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: desktop screen inventory + inspector-add vs cart-commit; Duplicates membership; Batch Scan + Tips content - [x] UI Designer: lock md+ workstation (tokens, not hex) from the attached mock; light + dark; Tips, device picker, batch progress, three-tab strip - [x] UX: Auto-detect off, Rescan, empty state, leave-with-queue, keyboard on desk controls, batch cancel, duplicate actions - [x] Architect: briefs + `slice_dependencies`; confirm Layout is `chrome="default"` on md+ only - [ ] Desktop composition in `pages/scanner.js` (do not hide sidebar / top bar at md+) - [ ] Camera as a framed panel; `deviceId` picker + Upload + Batch Scan + Auto-detect; hide mobile overlay chrome at md+ - [ ] Live match inspector wired to the latest unprocessed identify (condition / foil already on the cart entry) - [ ] Bottom strip: Recent Scans + Scan Queue + Duplicates over `useScannerQueue` / `ownershipMap` / `scanner-session` - [ ] Scanner Tips popover/modal with convoy-authored copy - [ ] Sequential multi-file Batch Scan through `identifyFromGalleryFile` (cancellable, queue progress) - [ ] Tests for desktop composition (inspector + queue + duplicates + batch enqueue, no mobile sheet) and no-regression on checkout sheet at `max-width: 767px` - [ ] Visual-diff: desktop `/scanner` surface; refresh Linux baselines if the page is in the visual suite ## What exists today (conductor survey) `/scanner` is one route, two compositions, one engine. | Layer | Files | Today | | --- | --- | --- | | Page | `pages/scanner.js` | Auth gate; `Layout chrome="immersive"` on **all** viewports; camera column + `md:` 360px `ScannerReview` cart; mobile-only `ScannerCheckoutSheet` | | Layout | `components/Layout.js` | Immersive hides **mobile** nav + top bar (`max-md` only). Desktop sidebar + TopSearchBar already stay visible. | | Camera chrome | `components/scanner/ScannerCamera.js` | Full-bleed video, overlay top bar (back / title / gallery), bottom bar (flash / facing / status / Review N), scan peek, disambiguation. Same chrome on desktop. | | Cart | `lib/use-scanner-queue.js`, `lib/scanner-session.js` | Identify enqueues locally (`processed: false`). Commit via `commitSelectedToOwned` / `commitSelectedToCollection`. `sessionStorage` persist. `addSingleCardToOwned` already exists. | | Identify | `lib/use-scanner-identification.js`, `lib/use-camera-scanner.js` | Facing-mode swap only — **no** `deviceId` / `enumerateDevices`. Auto-detect is always on unless `verificationPausedRef` (checkout / list picker / disambiguation). | | Review | `components/scanner/ScannerReview.js` | Thin wrapper: desktop side panel titled "Cart" that mounts `ScannerCheckoutContent`. | Mobile checkout decisions that still apply unless IA overturns them for desktop only: stay on camera after commit (D1), skip Setup (D3), My Collection + List only (D4), gallery in-scope (D5), cart in `sessionStorage` (D7). ## Conductor notes (build shape) Likely file ownership for Architect to refine: | Area | Files | | --- | --- | | Viewport split | `pages/scanner.js` — `chrome` default on md+, immersive on mobile; desktop grid vs mobile overlay | | Camera panel | `components/scanner/ScannerCamera.js` (desktop variant or `variant="workstation"`), `lib/use-camera-scanner.js` (`enumerateDevices` + `deviceId`) | | Inspector | new `components/scanner/ScannerResultPanel.js` — latest cart entry + condition/foil + add/rescan | | History strip | new `components/scanner/ScannerHistoryStrip.js` — Recent / Queue / Duplicates over `queue.scannedCards` + `ownershipMap` | | Batch Scan | `lib/use-scanner-identification.js` (`identifyFromGalleryFile` loop), queue progress UI | | Tips | new `components/scanner/ScannerTips.js` — `` or popover, convoy copy | | Cart reuse | `lib/use-scanner-queue.js`, `lib/scanner-session.js`, `ScannerCheckoutSheet.js` (mobile only) | | Copy | `lib/collection-vocabulary.js` | Do not rewrite identification. Prefer a desktop layout shell that **hides** mobile overlay chrome at `md+` rather than forking the camera hook. ## Decisions (post-conductor) Locked 2026-08-15 from the parent session. IA / UX / Architect treat these as settled. | # | Decision | | --- | --- | | C1 | **Batch Scan is in.** Multi-file sequential identify via the existing gallery path. No new batch API, no parallel Gemini, no new pile detector. | | C2 | **Duplicates tab is in.** Strip tab with a badge. Membership = already-owned (`ownershipMap`) and/or same-session name+set repeats. IA picks the exact rule and tab actions. | | C3 | **Scanner Tips is in.** Header control → glass popover/modal. Copy in this convoy. | | C4 | **Real webcam device picker is in.** `enumerateDevices` + `deviceId` on desktop. Mobile keeps facing-mode swap. | | C5 | **Inspector can commit this card now** *and* the queue strip remains for multi-add (same cart, two commit surfaces). Overturn only if IA finds a conflict. | ## Open questions (IA / product) 1. **Duplicates membership + actions.** Owned-in-collection only, session repeats only, or both? From the tab, can the user still add (increment qty), skip, or jump the inspector to that row? 2. **Wishlist.** Out as a feature. Confirm "Save to Wishlist" → Add to List on the inspector, or omit the third action. 3. **Auto-detect toggle.** User-facing pause of identification (extend `verificationPausedRef`), or just a status badge? 4. **Batch Scan cancel / errors.** Mid-batch cancel: keep already- identified rows? Per-file failure: continue the rest and flag the row, or stop? 5. **Tips content.** Four or five short tips (lighting, frame the card, foil glare, hold still, auto-detect). IA drafts; UI Designer locks the surface. ## Multitask dispatch Planning is serial: IA → UI Designer → UX → Architect. After architect: implementer fan-out only if briefs have `depends_on: []` and disjoint `files:`. Device picker (`use-camera-scanner.js`) and Tips (`ScannerTips.js`) are the best candidates to parallelize with the inspector if they do not both own `pages/scanner.js`. Page composition likely blocks the strip and Batch Scan wiring. After PR draft: `/multitask` audit fan-out `role-reviewer + role-security-auditor + role-design-system-auditor + role-a11y-auditor` (group id: `audit-scanner-desktop-layout-`). ## IA ### Affected routes - `/scanner` — **[modified]** Single route, two viewport compositions. Desktop (`md+`) switches to `Layout chrome="default"` (sidebar + TopSearchBar visible), framed camera workstation, live match inspector (right rail), and bottom history strip. Mobile (`max-md`) stays `chrome="immersive"` with checkout sheet — no regression. - `/login` — **[impacted]** Existing `returnUrl=/scanner` auth gate unchanged; desktop users land on the workstation after sign-in. - `/collections`, `/my-cards` — **[impacted]** Post-commit navigation targets only (Add to List picker, success flows). No route or nav IA changes in this convoy. No new routes. No API route changes. ### User flow ```mermaid flowchart LR A["/scanner (auth)"] --> B{"md+?"} B -->|Yes| C["Workstation"] B -->|No| D["Immersive mobile"] C --> E["Scan / Upload / Batch"] E --> F["Match inspector"] F --> G["Add or queue"] C --> H["Strip tabs"] H --> F ``` Desktop path: user opens `/scanner` with app chrome → scans via webcam, single Upload Image, or Batch Scan (sequential gallery identify) → latest match appears in the right-rail inspector → commits one card via inspector **or** batches via Scan Queue strip → Duplicates tab surfaces owned + session-repeat rows for review/increment. Mobile path unchanged: full-bleed camera → checkout sheet. ### Screen inventory | Screen | Path | New/modified | Notes | | --- | --- | --- | --- | | Scanner Desktop Workstation | `/scanner` | modified | `md+` grid: framed camera panel (device picker, Upload, Batch Scan, Auto-detect toggle, Tips), right-rail inspector, bottom strip. Replaces 360px cart side panel as primary right-hand surface. | | Scanner Mobile Immersive | `/scanner` | impacted (no regression) | `max-md`: `chrome="immersive"`, overlay camera chrome, `ScannerCheckoutSheet`. D1/D3/D4/D5/D7 decisions preserved. | | Live Match Inspector | `/scanner` | new (sub-surface) | Right rail on desktop. Shows latest unprocessed identify: thumbnail, name, set, rarity, collector #, condition, foil, confidence. Actions: `VOCAB.ADD_TO_MY_COLLECTION`, `VOCAB.ADD_TO_LIST`, Rescan. Single-card commit without opening checkout sheet. | | History / Queue Strip | `/scanner` | new (sub-surface) | Bottom strip on desktop. Tabs: **Recent Scans** (session history), **Scan Queue** (uncommitted cart, badge = unprocessed count), **Duplicates** (badge = duplicate row count). Row click focuses card in inspector. | | Scanner Tips | `/scanner` | new (sub-surface) | Header control → glass `` or popover. Five convoy-authored tips (see Content deltas). No route change. | | List Picker | `/scanner` | impacted | Existing "Choose a List" ``. Opened from inspector `VOCAB.ADD_TO_LIST` on desktop (and unchanged on mobile). | | Leave Scanner | `/scanner` | impacted | Existing leave-with-uncommitted-queue modal. Applies to both viewports when navigating away with queue items. | ### Content / data model deltas **Copy (ship from `lib/collection-vocabulary.js`):** - Primary add: `VOCAB.ADD_TO_MY_COLLECTION` ("Add to My Collection"). - Secondary add: `VOCAB.ADD_TO_LIST` ("Add to List") — **not** "Save to Wishlist" (feature omitted). - Strip tab labels: "Recent Scans", "Scan Queue", "Duplicates". - Camera controls: "Upload Image", "Batch Scan", "Auto-detect" (toggle + status on/off), "Scanner Tips", "Rescan". - Device picker: "Camera" or "Webcam" `` or styled native picker, label **Camera** | `enumerateDevices` video inputs; desktop only | | Upload Image | `