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>
105 lines
4.1 KiB
Markdown
105 lines
4.1 KiB
Markdown
---
|
|
convoy: scanner-desktop-layout
|
|
brief_number: 4
|
|
depends_on: []
|
|
recommended_model: composer-2.5-fast
|
|
model_tier: fast
|
|
files:
|
|
- components/scanner/ScannerHistoryStrip.js
|
|
- test/components/ScannerHistoryStrip.test.js
|
|
cross_brief_commitments:
|
|
- brief: 6
|
|
description: |
|
|
Brief 6 supplies `focusedCardId`, `onFocusCard`, `batchProgress`
|
|
(`{ active, current, total, onCancel }`), bulk commit handlers, and
|
|
`onClearAll`. Row click calls `onFocusCard(card.id)` — no auto-commit.
|
|
---
|
|
|
|
# Brief 4: History / queue / duplicates strip
|
|
|
|
## Goal (1 sentence)
|
|
|
|
Build the bottom `ScannerHistoryStrip` with Recent Scans, Scan Queue, and Duplicates tabs over the existing cart model.
|
|
|
|
## Files in scope (do not edit anything else)
|
|
|
|
- `components/scanner/ScannerHistoryStrip.js`
|
|
- `test/components/ScannerHistoryStrip.test.js`
|
|
|
|
## Conventions to follow
|
|
|
|
- `GlassSurface` tint `mid` for strip container; chips use solid `--bg-secondary` fill — **no per-chip `backdrop-filter`**.
|
|
- Tab labels: **Recent Scans**, **Scan Queue**, **Duplicates** with badge counts on Queue and Duplicates.
|
|
- `role="tablist"` / `role="tab"` / `role="tabpanel"`; badges in `aria-label`
|
|
(e.g. `Scan Queue, 3 unprocessed cards`).
|
|
- Empty tab copy (UX §5): Recent → *No scans yet this session.* · Queue →
|
|
*Scan queue is empty — matches appear here before you add them.* · Duplicates →
|
|
*No duplicates detected.*
|
|
- **Clear All** text link right-aligned; disabled while `batchProgress?.active`.
|
|
- **View All Scans** ghost button at strip bottom scrolls chip row to end (v1 — no expanded modal).
|
|
- Chip row: horizontal `overflow-x-auto`, min height 44px, ember rim on focused chip
|
|
(`focusedCardId === card.id` → `--ember-rim-pronounced`).
|
|
- Row click → `onFocusCard(card.id)` only — never auto-commit.
|
|
- Batch progress UI (when `batchProgress.active`): inline banner in Scan Queue tabpanel —
|
|
*Scanning {current} of {total}…* determinate bar, ghost **Cancel** calling `batchProgress.onCancel`.
|
|
- Failed identify rows: `identifyFailed` flag on card (Brief 6 sets) → ember left border +
|
|
**Identify failed** label + optional reason `text-sm`.
|
|
|
|
## Duplicate membership helper (export from this file)
|
|
|
|
```js
|
|
export function getDuplicateCards(scannedCards, ownershipMap) {
|
|
const sessionRepeatIds = new Set();
|
|
const seen = new Map(); // key: name+set → first id
|
|
for (const card of scannedCards) {
|
|
const key = `${card.name}::${card.set}`;
|
|
if (seen.has(key)) sessionRepeatIds.add(card.id);
|
|
else seen.set(key, card.id);
|
|
if ((card.quantity || 1) > 1) sessionRepeatIds.add(card.id);
|
|
}
|
|
return scannedCards.filter(
|
|
(card) =>
|
|
sessionRepeatIds.has(card.id) ||
|
|
(card.databaseId && ownershipMap[card.databaseId])
|
|
);
|
|
}
|
|
```
|
|
|
|
**Recent Scans** tab: all `scannedCards` session history (processed + unprocessed), newest first.
|
|
**Scan Queue** tab: `!card.processed` rows; badge = unprocessed count.
|
|
**Duplicates** tab: `getDuplicateCards(...)`; badge = duplicate set length.
|
|
|
|
## Props shape
|
|
|
|
```js
|
|
export default function ScannerHistoryStrip({
|
|
scannedCards,
|
|
ownershipMap,
|
|
focusedCardId,
|
|
onFocusCard,
|
|
activeTab,
|
|
onTabChange,
|
|
batchProgress = null,
|
|
onClearAll,
|
|
onCommitSelectedToOwned,
|
|
onOpenListPicker,
|
|
isProcessing = false,
|
|
}) {
|
|
```
|
|
|
|
Bulk commit buttons in Queue tab footer: `VOCAB.ADD_TO_MY_COLLECTION` + `VOCAB.ADD_TO_LIST` calling parent handlers (Brief 6 wires `queue.commitSelectedToOwned`).
|
|
|
|
## Acceptance criteria
|
|
|
|
- [ ] Three tabs with correct labels and badge counts
|
|
- [ ] `getDuplicateCards` covers ownershipMap + session name/set repeats + quantity>1
|
|
- [ ] Row click invokes `onFocusCard` without commit
|
|
- [ ] Batch progress banner with Cancel when `batchProgress.active`
|
|
- [ ] Clear All disabled during active batch
|
|
- [ ] Chip focus ring matches `focusedCardId`
|
|
- [ ] tests: tab switching, duplicate helper, empty states, batch banner
|
|
- [ ] no scope expansion (do not edit files outside `files:` above)
|
|
|
|
## Rationale (≤3 sentences)
|
|
|
|
The strip is the second commit surface (C5) alongside the inspector. Centralizing duplicate logic in one exported helper keeps `pages/scanner.js` thin. Tab + chip UX is independent of camera device picker, so this brief parallelizes cleanly.
|