Give /scanner a md+ camera, live match inspector, and history strip (with device picker, batch scan, and tips) without regressing the mobile immersive checkout. Co-authored-by: Cursor <cursoragent@cursor.com>
54 KiB
| name | classification | success_metric | skip | status | created | depends_on | model_policy | design_direction | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| scanner-desktop-layout | feature | 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. | open | 2026-08-15 |
|
|
|
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) stayschrome="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+deviceIdinuseCameraScanner(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<select>. - Upload Image + Batch Scan. Upload Image stays single-file
(existing gallery path). Batch Scan is a multi-file picker
that runs each image through
identifyFromGalleryFilesequentially (respect existing identify rate limits; no parallel Gemini storm, no new batch API). Progress and failures show in Scan Queue. Live multi-card on the webcam stays Auto-detect — do not build a new pile-in-one-frame detector. - Scanner Tips. Header control from the mock. Glass popover
or modal (use
<Modal>/GlassSurface, no custom scrim) with short lighting / framing / foil / auto-detect guidance. No new route. Copy authored in this convoy (IA + UI Designer). - Live match inspector (right rail). Current identify result: thumbnail, name, set, rarity, collector number, condition, foil, confidence, primary add, Rescan. This replaces the cart list as the primary right-hand surface.
- Bottom history / queue strip. Three tabs over the existing
cart + ownership model: Recent Scans (session history),
Scan Queue (uncommitted cart), Duplicates (already in
My Collection via
ownershipMap, and/or same-session name+set repeats frommergeScannedCardEntry). Badge counts on Queue and Duplicates. Clear + view-all as IA/UX refine. - Reuse, don't rewrite, the scan engine. Same hooks:
useCameraScanner,useScannerIdentification,useScannerQueue,scanner-session. Identify / OCR / Gemini stay out.
Out of scope
- Identify / detection / Gemini (
lib/scanner-card-identify.js,pages/api/scan/identify.js, OpenCV). Sibling:scanner-identify-upgrade. - Mobile immersive checkout, scan peek, or checkout sheet — no
visual or interaction regression below
md. - Layout nav IA, Daily Ember, notifications, profile cluster.
Sibling:
dashboard-home-realignment. Mock items Wishlist / Trades / Market / Events / Binders are not product nav. - Wishlist as a feature (no table / API). Mock "Save to Wishlist" maps to Add to List or drops — IA decides.
- A new identify / batch-OCR API, parallel Gemini calls, or a pile-in-one-frame detector. Batch Scan is multi-file sequential identify on the existing path.
- Schema / new tables. Cart remains client session state.
- Changing the homepage visual-diff baseline except as a
side-effect of
/scannerdesktop chrome (scanner surfaces only).
Product-vocab lock (from mock → Deck Hearth)
| Mock copy | Ship as |
|---|---|
| Add to Collection | VOCAB.ADD_TO_MY_COLLECTION |
| Save to Wishlist | Add to List, or omit |
| Binders (nav) | Lists — not this convoy |
| Collection (nav) | My Collection — not this convoy |
Roles invoked
role-ia-architect— desktop flow vs mobile cart; inspector vs queue commit; Duplicates membership (ownershipMapvs session repeat); Batch Scan progress / failure; Tips content outline; device-picker empty/denied states.role-ui-designer— lock the md+ workstation against the attached mock using Liquid Glass tokens (ui-ux-pro-maxis installed), including Tips popover, device<select>, Batch Scan progress, and the three-tab strip. Do not skipui-design.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).role-architect— briefs. Likely: (1) page composition + Layout chrome split, (2) camera panel +deviceIdpicker, (3) result inspector, (4) history/queue/duplicates strip, (5) batch multi-file identify + Tips.slice_dependenciesmust mark what can run in parallel.role-implementer— per brief.- Audit fan-out: reviewer + security-auditor + design-system-auditor
- a11y-auditor.
Todos
- IA: desktop screen inventory + inspector-add vs cart-commit; Duplicates membership; Batch Scan + Tips content
- UI Designer: lock md+ workstation (tokens, not hex) from the attached mock; light + dark; Tips, device picker, batch progress, three-tab strip
- UX: Auto-detect off, Rescan, empty state, leave-with-queue, keyboard on desk controls, batch cancel, duplicate actions
- Architect: briefs +
slice_dependencies; confirm Layout ischrome="default"on md+ only - Desktop composition in
pages/scanner.js(do not hide sidebar / top bar at md+) - Camera as a framed panel;
deviceIdpicker + 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
/scannersurface; 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 — <Modal> 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)
- 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?
- Wishlist. Out as a feature. Confirm "Save to Wishlist" → Add to List on the inspector, or omit the third action.
- Auto-detect toggle. User-facing pause of identification
(extend
verificationPausedRef), or just a status badge? - Batch Scan cancel / errors. Mid-batch cancel: keep already- identified rows? Per-file failure: continue the rest and flag the row, or stop?
- 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-<pr>).
IA
Affected routes
/scanner— [modified] Single route, two viewport compositions. Desktop (md+) switches toLayout chrome="default"(sidebar + TopSearchBar visible), framed camera workstation, live match inspector (right rail), and bottom history strip. Mobile (max-md) stayschrome="immersive"with checkout sheet — no regression./login— [impacted] ExistingreturnUrl=/scannerauth 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
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 <Modal> or popover. Five convoy-authored tips (see Content deltas). No route change. |
| List Picker | /scanner |
impacted | Existing "Choose a List" <Modal>. 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"
<select>label; empty/denied fallback copy (UI Designer).
Scanner Tips content (draft for UI Designer):
- Good lighting — avoid glare on foil cards.
- Fill the frame with one card; keep corners visible.
- Hold still until Auto-detect locks the match.
- Use Batch Scan for a pile of photos from your gallery.
- Switch webcam if the image is dark or mirrored.
No schema changes. Cart remains client sessionStorage via lib/scanner-session.js. Duplicates derive from existing ownershipMap (already in My Collection) and mergeScannedCardEntry (same-session name+set repeats) — no new tables or API fields.
IA decisions (locked — formerly open questions):
| Topic | Decision |
|---|---|
| Duplicates membership | Both sources: ownershipMap (already-owned) and same-session name+set repeats via mergeScannedCardEntry. Badge = count of rows in the Duplicates set. Row click focuses that card in the inspector. User can still add (increment qty) from inspector or queue. |
| Wishlist | Omit. Inspector third action is VOCAB.ADD_TO_LIST, not wishlist. |
| Auto-detect | Real pause toggle on desktop. Extends verificationPausedRef (user off = paused identify). Status badge reflects on/off. Mobile behavior unchanged unless UX specifies otherwise. |
| Batch cancel / errors | Mid-batch cancel keeps already-identified rows. Per-file failure continues the rest and flags that row in Scan Queue — do not stop the batch. |
| Inspector vs queue commit | Both surfaces (C5): inspector commits one card now; Scan Queue strip handles multi-add / bulk commit. Same cart, two commit paths. |
| Webcam picker | Desktop only: enumerateDevices + deviceId. Mobile keeps facing-mode swap (C4). |
Open IA questions
None — all five conductor questions resolved above.
Design direction
Locked v1 against image-1eeebe23-9983-4a96-a3e7-4e3cdfdceb5b.png
(dark + light side-by-side). Incremental redesign of an existing
Liquid Glass surface — not a new palette. Generator run:
desktop trading card scanner workstation camera inspector queue glassmorphism (ui-ux-pro-max v2.5.0).
Summary
On md+, /scanner is a desk workstation inside normal app
chrome (sidebar + TopSearchBar): a framed live-camera panel with
ember corner brackets, a right-rail live match inspector, and a
bottom history / queue strip — inspect one card, commit from the
rail, or batch from the strip. Mobile stays immersive; this direction
applies only at md+.
Pattern + style
| Field | Value |
|---|---|
| Product type | Desktop trading card scanner workstation |
| Landing / app pattern | Feature-Rich Showcase → workstation grid (camera + inspector + strip; not a marketing landing) |
| UI style | Liquid Glass (existing Deck Hearth DS) — translucent panels, rim-light, ember accent rings |
| Stack notes | nextjs · React 18 · Tailwind + CSS variables · <Modal> / <GlassSurface> / <Button> primitives |
Generator vs repo (repo wins):
| Generator | Locked override |
|---|---|
| Exaggerated Minimalism (oversized type, massive whitespace) | Rejected — match existing app density; page title text-2xl / font-semibold, subtitle text-sm text-secondary |
Palette #1E293B / #2563EB scan blue |
Rejected — use --accent-ember, --accent-flame, --accent-gold, --bg-*, --text-* from styles/globals.css |
| Inter typography | Rejected — keep system stack (-apple-system, BlinkMacSystemFont, …) |
| Feature-Rich Showcase sections | Adapted — three functional zones (camera, inspector, strip) replace marketing feature cards |
Layout authority (md+ only)
Viewport split at md (768px). Below md, no changes (immersive +
checkout sheet).
┌─────────────────────────────────────────────────────────────────┐
│ [Layout sidebar] │ TopSearchBar + profile │
├──────────────────┼──────────────────────────────────────────────┤
│ │ Card Scanner [Scanner Tips] │
│ │ <subtitle> │
│ ├──────────────────────────┬───────────────────┤
│ │ ┌─ Auto-detect ON ─┐ │ Scan Result 98% │
│ │ │ [live video] │ │ ┌────┐ metadata │
│ │ │ ember brackets │ │ │thumb│ set/rarity │
│ │ └──────────────────┘ │ condition ▾ foil │
│ │ [Camera ▾][Upload][Batch] │ confidence bar │
│ │ [Auto-detect]│ + Add to My Coll. │
│ │ │ Rescan | Add List │
│ ├──────────────────────────┴───────────────────┤
│ │ Recent │ Queue (N) │ Dupes (N) Clear All│
│ │ [chip][chip][chip]… │
│ │ [ View All Scans ] │
└──────────────────┴──────────────────────────────────────────────┘
| Zone | Width / placement | Surface recipe |
|---|---|---|
| Page header | Full content width above grid | Flat on --bg-primary; Tips = <Button variant="ghost"> + lightbulb icon |
| Camera column | flex-1 / min-w-0; ~60–65% |
Viewport frame: --glass-surface-low + --ember-rim-subtle on outer frame; ember corner brackets on video (existing ScannerCamera) |
| Desk control bar | Directly under viewport | --glass-surface-mid bar, rounded-xl, horizontal flex wrap at md |
| Inspector rail | Fixed w-[360px] or max-w-sm; sticky top optional |
--glass-surface-low panel, --elevation-ambient, --rim-light-inner/outer |
| Bottom strip | Full content width below main grid | --glass-surface-mid container; tab row + chip scroller |
Page copy (header):
- Title: Card Scanner
- Subtitle: Identify cards with your webcam and add them to My Collection.
Colors
All values via CSS variables — no hex in .js.
| Role | Token | Usage on this surface |
|---|---|---|
| Page background | --bg-primary |
Workstation canvas behind glass panels |
| Panel fill | --glass-surface-{low,mid,high} |
Camera frame (low), control bar + strip (mid), popovers (high) |
| Primary text | --text-primary |
Titles, card names, tab labels |
| Secondary text | --text-secondary |
Subtitle, set/rarity, timestamps |
| CTA / primary action | --accent-ember |
Add to My Collection button; active tab underline; selected chip border |
| Accent highlight | --accent-flame, --accent-gold |
Confidence bar fill; match badge when ≥90% |
| Interactive rim | --ember-rim-subtle / --ember-rim-pronounced |
Camera frame rest; primary button hover/focus |
| Success / match | --accent-gold or existing success token |
Auto-detect ON dot; high-confidence pill |
| Error / batch fail | --accent-ember at reduced opacity or danger variant |
Failed queue row indicator |
| Border | --border |
Control bar dividers, chip separators |
Light and dark both use the same token names; theme context switches
values. Mock's cool-navy dark is approximated by existing
--bg-primary-dark — do not introduce a new dark base.
Typography
| Role | Font | Notes |
|---|---|---|
| Page title | System stack, text-2xl font-semibold |
"Card Scanner" |
| Subtitle | System stack, text-sm, --text-secondary |
One line under title |
| Section labels | text-sm font-medium uppercase tracking-wide |
"Scan Result", strip tab labels |
| Card name (inspector) | text-lg font-semibold |
Primary identify headline |
| Metadata | text-sm, --text-secondary |
Set · rarity · collector # |
| Chip / queue row | text-sm name, text-xs meta |
Timestamps right-aligned |
Camera viewport + desk controls
Inside frame (overlay on video):
- Auto-detect badge — top-left pill: green status dot +
Auto-detect ON/Auto-detect OFF(reflects pause toggle). - Ember corner brackets — four L-shaped corners on the card
alignment region (ship existing
ScannerCamerabracket styling). - Optional alignment guide (mock dotted vertical line) — omit in v1 unless implementer brief explicitly includes it; brackets + badge are required.
Desk control bar (below frame, left → right):
| Control | Component | Notes |
|---|---|---|
| Camera | <select> or styled native picker, label Camera |
enumerateDevices video inputs; desktop only |
| Upload Image | <Button variant="secondary"> + upload icon |
Single-file; existing gallery path |
| Batch Scan | <Button variant="secondary"> + stack icon |
Multi-file picker; triggers sequential identify |
| Auto-detect | Toggle switch + label Auto-detect | Right-aligned on wide screens; extends verificationPausedRef |
Webcam device picker — fallback copy (locked):
| State | Message | Action hint |
|---|---|---|
| Loading | Detecting cameras… | Disable <select> until resolved |
| Empty list | No camera found | Connect a webcam or use Upload Image. |
| Permission denied | Camera access blocked | Allow camera in your browser settings, or use Upload Image. |
| Error (enumerate failed) | Couldn't list cameras | Use Upload Image or reload the page. |
When empty or denied, render the message inline below the disabled
<select> (text-sm, --text-secondary); do not use a blocking
modal for device errors.
Live match inspector (right rail)
Header row: Scan Result + confidence pill (e.g. 98% Match).
Empty state (no identify yet): centered illustration area + "Point your camera at a card or upload an image to see a match."
Populated state (latest unprocessed cart entry):
- Thumbnail + name, set, rarity icon, collector number
- Condition —
<select>(existing condition options) - Foil — toggle switch
- Confidence — percentage + horizontal bar + caption (Excellent match. / Good match. / Low confidence — verify before adding.)
- Actions (top → bottom priority):
- Primary:
VOCAB.ADD_TO_MY_COLLECTION(<Button variant="primary">,+icon optional) - Secondary row: Rescan (ghost) ·
VOCAB.ADD_TO_LIST(ghost)
- Primary:
- Never ship "Add to Collection", "Save to Wishlist", or "Mark Owned".
Bottom history / queue strip
Tab row: Recent Scans · Scan Queue (N) · Duplicates
(N) — badge = unprocessed count / duplicate row count per IA.
Clear All — text link, right-aligned (text-sm, destructive on
hover).
Card chips (horizontal scroll, overflow-x-auto):
- Thumbnail, name, set · rarity · condition snippet
- Status: confidence % + relative time (Just now, 2 min ago)
- Active / focused row:
--ember-rim-pronouncedborder (matches mock ember outline on leftmost chip) - No
backdrop-filteron individual chips — solid--bg-secondaryper per-card grid performance budget (docs/DESIGN_TOKENS.md§ Per-card grid performance budget)
View All Scans — full-width ghost button at strip bottom; opens expanded queue view or scrolls strip — Architect brief decides; visual = translucent bar button from mock.
Row click — focuses that card in the inspector (IA-locked).
Batch Scan progress (Scan Queue tab)
When batch is running, Scan Queue tab auto-focuses (or shows an inline banner):
- Progress line: Scanning 3 of 12… with determinate progress bar
(
--accent-emberfill) - Cancel — ghost button; keeps already-identified rows (IA)
- Per-file failure — enqueue row with error flag, label Identify
failed,
text-smreason if available; batch continues remaining files - On complete — banner dismisses; failed rows stay in queue with visual error state (ember left border or warning icon)
Scanner Tips (surface locked)
<Modal> — five tips exceed popover length; use
components/ui/Modal + modal panel recipe (--glass-surface-low,
--modal-scrim). Trigger: header Scanner Tips button (lightbulb
icon + label).
| # | Tip |
|---|---|
| 1 | Good lighting — avoid glare on foil cards. |
| 2 | Fill the frame with one card; keep corners visible. |
| 3 | Hold still until Auto-detect locks the match. |
| 4 | Use Batch Scan for a pile of photos from your gallery. |
| 5 | Switch camera if the image is dark or mirrored. |
Modal title: Scanner Tips. Dismiss via close control + Esc +
scrim click (standard <Modal> behavior).
Effects + motion
- Panel transitions: 150–200ms ease on hover/focus for buttons and
chips (
docs/MOTION_SYSTEM.mdif defined; elsetransition-colors duration-200) - Auto-detect badge dot: subtle pulse when ON; respect
prefers-reduced-motion(static dot when reduced) - Confidence bar: width transition 300ms on value change; no animation when reduced
- Tab switch: instant content swap (no slide); optional 150ms fade on chip row refresh
- Hover:
cursor-pointeron all clickable chips, tabs, buttons;--ember-rim-subtle→--ember-rim-pronouncedon primary hover
Anti-patterns (do not ship)
- Full-bleed camera overlay on desktop (mobile-only chrome)
- Hex colors in
.js/ inline styles - Generator slate/blue palette or Inter font import
backdrop-filteron per-card strip chips or thumbnail tiles- "Add to Collection", "Save to Wishlist", "Mark Owned", "All My Cards" in UI copy
- Custom modal scrim (must use
<Modal>primitive) - Popover for five tips (use Modal)
- Parallel batch identify UI implying concurrent Gemini calls
- Device
<select>on mobile (facing-mode toggle stays)
Pre-delivery checklist
- No emojis as icons (SVG: Lucide / Heroicons)
cursor-pointeron clickable elements- Hover/focus transitions 150–300ms
- Text contrast ≥ 4.5:1 on glass-over-flat surfaces
- Keyboard focus visible (
--ember-rim-pronouncedfocus ring) prefers-reduced-motionrespected (badge pulse, bar animate)- Responsive: mobile immersive unchanged; workstation at 768 / 1024 / 1440
- Light + dark verified against mock reference
- Copy from
lib/collection-vocabulary.jsfor add actions - Visual-diff: desktop
/scannerif in suite
Conflict rule
Repo design tokens win when they disagree with generator output.
This lock intentionally overrides ui-ux-pro-max palette and typography
only. Layout proportions follow the attached mock; nav items in the
mock that are not product routes (Wishlist, Trades, etc.) are not
in scope — existing Layout sidebar IA stands.
UX
1. Existing components to reuse
| Component | Path | Use on desktop workstation |
|---|---|---|
<Modal> |
components/ui/Modal.js |
Leave-with-queue, List picker, Scanner Tips (five tips — no custom scrim) |
<Button> |
components/ui/Button.js |
Upload Image, Batch Scan, Auto-detect toggle label area, inspector primary/ghost actions, batch Cancel, View All Scans |
<GlassSurface> |
components/ui/GlassSurface.js |
Camera frame, desk control bar, inspector rail, strip container, strip chips (solid --bg-secondary fill inside — no per-chip blur) |
<Input> |
components/ui/Input.js |
Not required v1; strip search deferred to Architect brief |
ScannerCamera |
components/scanner/ScannerCamera.js |
Framed viewport, ember brackets, overlay Auto-detect badge; hide mobile overlay chrome at md+ via variant prop |
ScannerDisambiguation |
components/scanner/ScannerDisambiguation.js |
Unchanged multi-match picker; pauses identify via existing verificationPausedRef wiring |
ScannerToast |
components/scanner/ScannerToast.js |
Post-commit confirmation, batch complete summary, identify errors |
ScannerCheckoutSheet |
components/scanner/ScannerCheckoutSheet.js |
Mobile only (max-md) — do not mount at md+ |
ScannerCountPill |
components/scanner/ScannerCountPill.js |
Mobile only — desktop uses strip Queue badge instead |
ReviewCardItem |
components/scanner/ReviewCardItem.js |
Pattern donor for condition <select>, foil toggle, confidence ring/color helpers, increment/decrement — lift constants (CONDITION_OPTIONS, confidencePercent, confidenceRingColor) into shared export or duplicate minimally in ScannerResultPanel |
| Leave modal + List picker | pages/scanner.js |
Reuse verbatim markup and handlers; extend leave trigger to desktop back/nav paths |
New surfaces (ScannerResultPanel, ScannerHistoryStrip, ScannerTips) compose primitives only — no new modal shell, no hex in .js.
2. Design direction alignment
- Liquid Glass tokens: All panels use
--glass-surface-{low,mid,high},--ember-rim-{subtle,pronounced},--elevation-ambientper Design direction layout authority table. Inspector rail =GlassSurfacetintlow; desk bar + strip =mid. - Ember accent: Primary add, active tab underline, focused chip border, Auto-detect ON dot use
--accent-ember/--accent-flame— not generator slate/blue. - Copy lock: Inspector and strip actions import
VOCAB.ADD_TO_MY_COLLECTIONandVOCAB.ADD_TO_LISTfromlib/collection-vocabulary.js. Wishlist omitted (IA-locked). - Performance budget: Strip chips and inspector thumbnail tiles use solid
--bg-secondary— nobackdrop-filteron per-card elements (Design direction +docs/DESIGN_TOKENS.md). - Tips surface:
<Modal size="md">with glass panel recipe — matches Design direction "Popover for five tips" rejection. - Motion: 150–200ms color/rim transitions; badge pulse and confidence bar width animate only when
prefers-reduced-motion: no-preference(see §7 interaction patterns).
3. Existing patterns to follow
- Viewport split:
pages/scanner.jsalready gates mobile checkout withmd:hidden/hidden md:flex— extend toLayout chrome="default"atmd+only; keepchrome="immersive"belowmd. - Pause identify:
verificationPausedRefinpages/scanner.js(lines 59–63) — extend desktop Auto-detect OFF to set ref; keep existing pauses for list picker + disambiguation + mobile checkout. - Single-card commit:
queue.addSingleCardToOwned(card)inlib/use-scanner-queue.js— inspector primary action; same API path as mobile bulk commit. - Leave with queue: Existing
<Modal title="Leave scanner?">+clearScannerCartStorage()— trigger from desktop back, sidebar nav away, androuter.back()whenunprocessedCount > 0. - List commit: Existing List picker
<Modal>+handleListPick— inspectorVOCAB.ADD_TO_LISTopens same modal scoped to focused inspector card (single selection), not bulk strip selection. - Error alerts: Match
ScannerCheckoutContent/pages/scanner.jslist picker —role="alert"div withcolor-mix(in srgb, var(--color-error) …)for commit and batch failures. - Confidence UX: Reuse
ReviewCardItemthresholds:<70%ember warning,≥90%gold/high — caption text from Design direction (Excellent match. / Good match. / Low confidence — verify before adding.). - Device picker fallbacks: Inline
text-sm--text-secondarymessages below disabled<select>per Design direction table — not a blocking modal. - Focus rings:
Button/ChromeIconButtonpattern —focus-visible:ring-2+'--tw-ring-color': 'var(--accent-ember)'(components/scanner/ScannerCamera.js).
4. A11y constraints
Hand to role-a11y-auditor:
- 1.4.3 Contrast (Minimum): All
--text-primary/--text-secondaryon--glass-surface-*over--bg-primarymust meet 4.5:1 for body text, 3:1 for large/bold card name (text-lg font-semibold). Confidence bar track vs fill must meet 3:1 non-text contrast. - 1.4.10 Reflow: At 768px width with 200% zoom, workstation grid stacks camera above inspector; strip tabs remain horizontally scrollable without two-dimensional scroll traps.
- 1.4.11 Focus Not Obscured (Minimum): Sticky inspector rail and batch progress banner must not fully hide focused desk controls or strip chips.
- 2.1.1 Keyboard: Every action reachable without pointer: desk controls, strip tabs (tablist), chip rows, inspector add/rescan, batch Cancel, Tips open/close.
- 2.4.3 Focus Order: DOM order = visual order: page header (Tips) → camera frame → desk bar (Camera → Upload → Batch → Auto-detect) → inspector → strip tabs → chip scroller → View All.
- 2.4.7 Focus Visible: Ember focus ring on all interactive elements; native
<select>getsfocus-visible:ring-2wrapper if browser default ring is suppressed. - 2.4.11 Focus Not Obscured: Modal open (Tips, leave, list picker) uses existing
useFocusTrap— no focus escape to camera video underneath. - 4.1.2 Name, Role, Value:
- Auto-detect toggle:
role="switch",aria-checked={!verificationPausedRef}, visible label Auto-detect associated viahtmlFor/aria-labelledby. - Strip tabs:
role="tablist"/role="tab"/role="tabpanel"witharia-selected,aria-controls, badge counts inaria-label(e.g.Scan Queue, 3 unprocessed cards). - Camera
<select>:<label>Camera +aria-describedbypointing to empty/denied fallback text when present. - Inspector condition
<select>: accessible name includes card name (Condition for {card.name}). - Foil toggle:
role="switch",aria-checked, label Foil.
- Auto-detect toggle:
- 4.1.3 Status Messages: Auto-detect ON/OFF badge exposes
aria-live="polite"region; batch progress (Scanning 3 of 12…) usesaria-live="polite"; commit success viaScannerToastwithrole="status". - 3.3.1 Error Identification: Batch per-file failures show Identify failed + reason text on the queue row; commit errors in inspector use
role="alert"(same pattern as checkout footer). - 2.5.3 Label in Name: Visible button text must appear in accessible name — e.g. Upload Image, not icon-only without
aria-label. - Live region discipline: Only one polite live region for batch progress; avoid duplicate announcements on tab auto-focus during batch.
5. Interaction patterns
Locked decisions (opinionated — one pattern each)
| Topic | Decision | Nielsen / rationale |
|---|---|---|
| Empty inspector | Centered muted camera/scan illustration + single line: "Point your camera at a card or upload an image to see a match." No duplicate Upload/Batch CTAs in the rail (controls live in desk bar). | H8 aesthetic minimalism; H6 recognition — one place for actions. |
| Leave with uncommitted queue | Keep existing <Modal title="Leave scanner?"> — Keep scanning (secondary) / Leave (danger). Same copy and clearScannerCartStorage() behavior on desktop nav-away. |
H3 user control; H5 error prevention. |
| Inspector add success | Advance inspector to next unprocessed queue entry (or empty state if queue clear). Show ScannerToast (Added to My Collection). Committed card appears in Recent Scans with processed styling. Do not linger on committed card in inspector. |
H2 real-world scan rhythm; H1 toast + strip update confirm status without blocking next decision. |
| Auto-detect OFF | Real pause via verificationPausedRef — no identify/OCR while OFF. Badge reads Auto-detect OFF; dot static gray. Toggle is independent of mobile checkout pause logic. |
H3 user control; H1 badge reflects system state. |
| Rescan | Clears focus card's identify metadata and re-queues same physical capture path (webcam frame or re-read gallery source if batch row) — card stays in queue unprocessed. | H3 undo/recover from bad match. |
| Duplicate row click | Focuses that card in inspector (scroll chip into view, --ember-rim-pronounced on active chip). Add still allowed — increment qty via inspector primary or existing queue increment if duplicate already unprocessed. No separate "Skip" action in v1. |
H6 recognition over recall; H4 consistency with Queue tab. |
| Batch cancel | Ghost Cancel in Scan Queue tab progress banner. Cancels remaining files; keeps already-identified rows (IA-locked). Banner dismisses; toast Batch scan stopped (info). | H3 user control; H9 recover from long batch. |
| Batch per-file failure | Row stays in Scan Queue with ember left border + Identify failed label; batch continues. Row click focuses inspector for manual Rescan or remove. | H9 graceful recovery; H1 visible error on row. |
| Strip row click (all tabs) | Sets inspector focus to that card; does not auto-commit. Recent / Queue / Duplicates share one focus model. | H4 consistency. |
| Clear All | Destructive text link — confirm via existing pattern or inline window.confirm only if Architect brief adds it; default: immediate clear with undo not required v1 (queue is session-scoped). Architect to confirm in brief. |
H5 prevent accidental loss — if no confirm, disable when batch running. |
Required
- Loading — identify in flight (H1): Inspector shows skeleton/thumbnail placeholder + Identifying… on desk bar Upload/Batch busy states disable repeat picks.
- Loading — commit (H1): Inspector primary
<Button loading>duringaddSingleCardToOwned; disable Rescan and secondary actions while processing. - Empty strip tabs (H9): Recent: No scans yet this session. Queue: Scan queue is empty — matches appear here before you add them. Duplicates: No duplicates detected.
- Auto-detect ON feedback (H1): Green pulsing dot in frame badge (respect reduced motion); optional success chime already in
ScannerCamera— desktop may keep muted default. - Batch running (H1): Auto-select Scan Queue tab; determinate progress bar; Cancel visible throughout.
- Focus management (H3): Opening Tips/list/leave modals traps focus; closing returns focus to trigger button.
- Hover/focus (H4): Chips and tabs:
--ember-rim-subtle→--ember-rim-pronouncedon hover/focus;cursor-pointeron all click targets.
Nice-to-have
- Optimistic UI: Inspector advance on commit can happen after API success only (no optimistic skip) — queue hook already marks
processedpost-success. - Keyboard shortcut:
Ato trigger inspector Add to My Collection when inspector populated and not processing — defer unless Architect brief adds; not v1 blocker. - View All Scans: Expanded modal or full-width strip — Architect decides; v1 may scroll chip row only.
6. Anti-patterns to avoid
- Duplicate CTAs in empty inspector — Upload/Batch already in desk bar (H8 minimalist design).
- Custom modal scrim for Tips or device errors — must use
<Modal>or inline text only (H4 consistency). - Device
<select>on mobile — facing-mode swap stays (H4). - Popover for five Scanner Tips — too long; Modal only (H8).
- Linger on committed card in inspector after add — blocks scan rhythm (H2).
- Stop entire batch on one file failure — IA forbids (H9).
- Discard identified rows on batch cancel — IA forbids (H3).
- "Save to Wishlist" / "Mark Owned" / "Add to Collection" copy — CI forbidden strings (H4).
backdrop-filteron strip chips or inspector thumbnail — GPU budget violation.- Full-bleed camera overlay on desktop — mobile-only chrome (H2).
- Parallel batch progress implying concurrent API calls — misleading (H1 honest status).
- Icon-only desk controls without
aria-label— fails 2.5.3 / 4.1.2. - Auto-commit on strip row click — user loses inspect step (H3 user control).
- Slide animation on tab switch — Design direction specifies instant swap; fade optional ≤150ms only.
7. Mobile / responsive notes
Below md (767px) — zero regression:
- Keep
Layout chrome="immersive",ScannerCameraoverlay chrome (back, gallery, flash, facing, Review N pill),ScannerCheckoutSheet,ScannerCountPill, scan peek, disambiguation sheet behavior unchanged. - Do not render desktop workstation grid, inspector rail, history strip, device
<select>, Batch Scan desk control, or Tips header button on mobile unless Tips is also added to mobile overlay (out of scope — Tips = desktop header only v1). verificationPausedRefmobile checkout pause unchanged (isCheckoutOpen && max-width 767px).
At md+ (768px+):
Layout chrome="default"— sidebar +TopSearchBarvisible.- Workstation grid: camera column
flex-1 min-w-0(~60–65%) + inspectorw-[360px] max-w-sm+ full-width bottom strip. - Hide mobile-only elements: checkout sheet, Review N pill, overlay top/bottom camera bars, facing-mode toggle (replace with Camera
<select>). - Desk control bar wraps at
md; Auto-detect toggle right-aligned atlg+. - Strip chip scroller:
overflow-x-auto,-webkit-overflow-scrolling: touchfor trackpad/touch on hybrid devices; min chip height 44px touch target. prefers-reduced-motion: Disable Auto-detect badge pulse, confidence bar width transition, optional chip-row fade — use@media (prefers-reduced-motion: reduce)instant state swaps; keep color/rim hover transitions ≤150ms or disable perdocs/MOTION_SYSTEM.mdif defined.- Breakpoints to verify: 768 / 1024 / 1440 — inspector sticky top optional; strip never collapses tabs into mystery meat menu v1.
- Visual-diff: Desktop
/scanneronly; mobile baseline must not change.
Architecture
File plan
| File | Action | Purpose |
|---|---|---|
lib/use-camera-scanner.js |
modified | enumerateDevices, deviceId selection, picker status, sessionStorage persist |
test/lib/use-camera-scanner.test.js |
new | Mock mediaDevices — device list, constraint switch, persist |
components/scanner/ScannerTips.js |
new | Header trigger + five-tip <Modal> |
test/components/ScannerTips.test.js |
new | Open/close, copy lock |
components/scanner/ScannerResultPanel.js |
new | Right-rail live match inspector |
test/components/ScannerResultPanel.test.js |
new | Empty/populated, add loading, vocab |
components/scanner/ScannerHistoryStrip.js |
new | Recent / Queue / Duplicates strip + getDuplicateCards |
test/components/ScannerHistoryStrip.test.js |
new | Tabs, duplicates helper, batch banner |
lib/scanner-batch-identify.js |
new | Sequential runSequentialGalleryIdentify |
test/lib/scanner-batch-identify.test.js |
new | Progress, cancel, error continuation |
pages/scanner.js |
modified | md+ workstation grid, focus state, pause composite, chrome split |
components/scanner/ScannerCamera.js |
modified | workstation variant — framed viewport, hide mobile overlay at md+ |
lib/use-scanner-identification.js |
modified | Remove redundant verificationPausedRef overwrite effect |
lib/use-scanner-queue.js |
modified | Boolean return from addSingleCardToOwned / addSingleCardToCollection |
test/pages/scanner.test.js |
new | Mobile checkout regression + desktop composition smoke |
test/components/ScannerCamera.test.js |
new | Workstation variant hides overlay chrome |
Not touched: components/Layout.js (immersive already equals default at md+),
lib/scanner-card-identify.js, pages/api/scan/identify.js, ScannerCheckoutSheet.js
(mobile only), nav IA (dashboard-home-realignment).
API surface
No new or modified API routes. Client cart remains sessionStorage via
lib/scanner-session.js. All commits reuse existing scanner-route-api.js helpers
(addScannedCardToOwned, addScannedCardToCollection).
Schema diff
None.
Test plan
| Area | File | What to lock |
|---|---|---|
| Device picker hook | test/lib/use-camera-scanner.test.js |
deviceId constraints, enumerate, persist, status messages |
| Batch helper | test/lib/scanner-batch-identify.test.js |
Sequential order, cancel mid-batch, error continues |
| Tips modal | test/components/ScannerTips.test.js |
Five tips, Modal primitive |
| Inspector | test/components/ScannerResultPanel.test.js |
Empty state (no duplicate CTAs), VOCAB labels |
| History strip | test/components/ScannerHistoryStrip.test.js |
getDuplicateCards, tab badges, batch banner |
| Camera variant | test/components/ScannerCamera.test.js |
Overlay hidden for workstation at md+ |
| Page orchestration | test/pages/scanner.test.js |
Checkout sheet at max-md; workstation surfaces at md+ |
| Mobile regression | test/components/ScannerCheckoutSheet.test.js |
Existing suite stays green — do not weaken |
| Layout chrome | test/components/Layout.test.js |
Existing immersive tests — no change expected |
Visual-diff: tests/visual/ contains only homepage.spec.ts / home.png —
no /scanner baseline update required for this convoy.
Risk list
| Risk | Mitigation |
|---|---|
verificationPausedRef race between page and useScannerIdentification |
Brief 6 deletes identification hook's direct ref assignment; page owns composite pause including isAutoDetectPaused |
addSingleCardToOwned lacks success signal today |
Brief 6 adds boolean return in use-scanner-queue.js |
chrome="immersive" on all viewports today |
Boot finding: Layout already shows sidebar at md+; page uses explicit default vs immersive via matchMedia for clarity |
| Batch identify failures invisible in queue | Page enqueues identifyFailed rows; strip shows ember border + label |
| Rescan without stored capture | v1: re-identify via scanImageUrl fetch; else toast to rescan from camera |
| Desktop/mobile class split regressions | Tests mock matchMedia; mobile paths keep md:hidden gates |
| Forbidden copy in new surfaces | Import VOCAB from collection-vocabulary.js; CI forbidden-stale-strings |
| Brief 6 LOC >400 | Accepted — single serializer for pages/scanner.js; components split across Briefs 2–4 |
getDuplicateCards over-counts |
Helper uses ownershipMap + session name/set + quantity>1; tune in strip tests |
Decomposition
| Brief # | Title | Files | Depends on | Est. PR size |
|---|---|---|---|---|
| 1 | Camera device picker hook | use-camera-scanner.js, test |
— | S (~180 LOC) |
| 2 | Scanner Tips modal | ScannerTips.js, test |
— | S (~100 LOC) |
| 3 | Live match inspector | ScannerResultPanel.js, test |
— | M (~280 LOC) |
| 4 | History / queue strip | ScannerHistoryStrip.js, test |
— | M (~300 LOC) |
| 5 | Sequential batch helper | scanner-batch-identify.js, test |
— | S (~120 LOC) |
| 6 | Page workstation wiring | scanner.js, ScannerCamera.js, identification + queue hooks, tests |
1, 2, 3, 4, 5 | L (~450 LOC) |
Estimated PRs: 6 (Briefs 1–5 parallelizable; Brief 6 after merge or rebase).
Slice dependencies (multitask-ready)
slice_dependencies:
- brief: 1
depends_on: []
files:
- lib/use-camera-scanner.js
- test/lib/use-camera-scanner.test.js
- brief: 2
depends_on: []
files:
- components/scanner/ScannerTips.js
- test/components/ScannerTips.test.js
- brief: 3
depends_on: []
files:
- components/scanner/ScannerResultPanel.js
- test/components/ScannerResultPanel.test.js
- brief: 4
depends_on: []
files:
- components/scanner/ScannerHistoryStrip.js
- test/components/ScannerHistoryStrip.test.js
- brief: 5
depends_on: []
files:
- lib/scanner-batch-identify.js
- test/lib/scanner-batch-identify.test.js
- brief: 6
depends_on: [1, 2, 3, 4, 5]
files:
- pages/scanner.js
- components/scanner/ScannerCamera.js
- lib/use-scanner-identification.js
- lib/use-scanner-queue.js
- test/pages/scanner.test.js
- test/components/ScannerCamera.test.js
Boot-the-brief findings (fixed in briefs)
- Layout chrome:
chrome="immersive"already renders sidebar + TopSearchBar atmd+(Layout.jsusesmax-md:hiddenonly). Brief 6 usesmatchMediato passchrome="default"on desktop for semantic clarity — noLayout.jsedit. verificationPausedRefoverwrite:useScannerIdentificationlines 62–66 set the ref toBoolean(disambiguation)only, racing page composite pause — Brief 6 removes that effect.addSingleCardToOwnedreturn: No success boolean today — Brief 6 extendsuse-scanner-queue.jsto returntrue/false.- Visual suite:
/scannernot intests/visual/— no Linux baseline refresh. - No new packages — dep-set check N/A; all primitives exist (
Modal,GlassSurface,Button). - Gallery batch path verified:
identifyFromGalleryFileexists inuse-scanner-identification.js(line 326) — Brief 5 wraps it sequentially.