deckhearth/.convoys/scanner-desktop-layout/brief-4-history-strip.md

106 lines
4.1 KiB
Markdown
Raw Permalink Normal View History

---
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.