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>
4.1 KiB
4.1 KiB
| convoy | brief_number | depends_on | recommended_model | model_tier | files | cross_brief_commitments | |||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| scanner-desktop-layout | 4 | composer-2.5-fast | fast |
|
|
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.jstest/components/ScannerHistoryStrip.test.js
Conventions to follow
GlassSurfacetintmidfor strip container; chips use solid--bg-secondaryfill — no per-chipbackdrop-filter.- Tab labels: Recent Scans, Scan Queue, Duplicates with badge counts on Queue and Duplicates.
role="tablist"/role="tab"/role="tabpanel"; badges inaria-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 callingbatchProgress.onCancel. - Failed identify rows:
identifyFailedflag on card (Brief 6 sets) → ember left border + Identify failed label + optional reasontext-sm.
Duplicate membership helper (export from this file)
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
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
getDuplicateCardscovers ownershipMap + session name/set repeats + quantity>1- Row click invokes
onFocusCardwithout 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.