---
convoy: scanner-desktop-layout
brief_number: 3
depends_on: []
recommended_model: composer-2.5-fast
model_tier: fast
files:
  - components/scanner/ScannerResultPanel.js
  - test/components/ScannerResultPanel.test.js
cross_brief_commitments:
  - brief: 6
    description: |
      Brief 6 passes `focusedCard`, `queue`, `isIdentifying`, handlers
      (`onAddToOwned`, `onAddToList`, `onRescan`), and `commitError`. On
      successful add, Brief 6 advances `focusedCardId` to the next unprocessed
      entry and fires `ScannerToast` — this component calls `onAddToOwned` only.
---

# Brief 3: Live match inspector rail

## Goal (1 sentence)

Build the right-rail `ScannerResultPanel` that inspects the focused cart entry and commits one card via `VOCAB.ADD_TO_MY_COLLECTION`.

## Files in scope (do not edit anything else)

- `components/scanner/ScannerResultPanel.js`
- `test/components/ScannerResultPanel.test.js`

## Conventions to follow

- Import `VOCAB` from `lib/collection-vocabulary.js` — never ship forbidden strings
  (`forbidden-stale-strings` CI).
- Primitives: `GlassSurface`, `Button` from `components/ui`.
- Duplicate minimally from `ReviewCardItem.js`: `CONDITION_OPTIONS`,
  `confidencePercent`, `confidenceRingColor` (do not edit `ReviewCardItem` in this brief).
- Confidence captions (Design direction): ≥90% *Excellent match.* · ≥70% *Good match.*
  · &lt;70% *Low confidence — verify before adding.*
- Inspector thumbnail + tiles: solid `--bg-secondary` — **no `backdrop-filter`** on per-card elements.
- Empty state: centered muted scan/camera SVG illustration + single line:
  *Point your camera at a card or upload an image to see a match.* — **no** Upload/Batch CTAs in rail.
- Populated: thumbnail, name (`text-lg font-semibold`), set · rarity · collector #,
  condition `<select>` (`aria-label` includes card name), foil `role="switch"`,
  confidence % + horizontal bar (300ms width transition; `@media (prefers-reduced-motion: reduce)` → instant).
- Actions: primary `VOCAB.ADD_TO_MY_COLLECTION` · ghost **Rescan** · ghost `VOCAB.ADD_TO_LIST`.
- Primary `Button` uses `loading` when `isAdding` (from `queue.addingCardIds.has(card.id)`).
- Commit errors: `role="alert"` div with `color-mix(in srgb, var(--color-error) …)` pattern from `pages/scanner.js`.
- `isIdentifying` state: show *Identifying…* placeholder when true and no focused card yet.

## Props shape (for Brief 6 wiring)

```js
export default function ScannerResultPanel({
  focusedCard = null,
  queue,
  isIdentifying = false,
  commitError = null,
  onAddToOwned,
  onAddToList,
  onRescan,
}) {
```

`onUpdateMetadata` via `queue.updateCardMetadata(focusedCard.id, patch)` inline.

## Acceptance criteria

- [ ] Empty state: illustration + one line only (no duplicate Upload/Batch)
- [ ] Populated state shows metadata, condition select, foil switch, confidence bar
- [ ] Primary button label is `VOCAB.ADD_TO_MY_COLLECTION`
- [ ] Secondary row: Rescan + `VOCAB.ADD_TO_LIST` (no wishlist)
- [ ] `prefers-reduced-motion` disables confidence bar width animation
- [ ] tests: empty state, populated card, add button disabled while adding
- [ ] no scope expansion (do not edit files outside `files:` above)

## Rationale (≤3 sentences)

The inspector replaces the 360px cart side panel as the primary right-hand surface on desktop. Keeping it a dumb view lets Brief 6 own focus/advance/toast orchestration. Confidence and condition UX matches checkout patterns users already know from `ReviewCardItem`.
