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>
7.5 KiB
| convoy | brief_number | depends_on | recommended_model | model_tier | files | cross_brief_commitments | ||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| scanner-desktop-layout | 6 |
|
composer-2.5-fast | fast |
|
|
Brief 6: Desktop workstation page wiring
Goal (1 sentence)
Wire pages/scanner.js into the md+ workstation grid (inspector + strip + desk controls) without regressing mobile immersive checkout.
Files in scope (do not edit anything else)
pages/scanner.jscomponents/scanner/ScannerCamera.jslib/use-scanner-identification.jslib/use-scanner-queue.jstest/pages/scanner.test.jstest/components/ScannerCamera.test.js
Conventions to follow
-
Layout chrome (boot finding):
chrome="immersive"already shows sidebar + TopSearchBar atmd+perLayout.js(max-md:hiddenonly on mobile nav). Use explicit viewport split for semantics:const [isDesktop, setIsDesktop] = useState(false); useEffect(() => { const mq = window.matchMedia('(min-width: 768px)'); const sync = () => setIsDesktop(mq.matches); sync(); mq.addEventListener('change', sync); return () => mq.removeEventListener('change', sync); }, []); // <Layout chrome={isDesktop ? 'default' : 'immersive'} />Smallest change — no
Layout.jsedits. -
Mobile zero regression: keep
ScannerCheckoutSheet, overlay camera chrome,ScannerCountPill, scan peek — allmd:hiddenormax-md:as today. -
Remove desktop
ScannerReviewside panel (hidden md:flexblock) — replaced by inspector + strip. -
Pause coordination (boot finding):
useScannerIdentificationcurrently has:useEffect(() => { if (verificationPausedRef) { verificationPausedRef.current = Boolean(disambiguation); } }, [disambiguation, verificationPausedRef]);Delete this effect — page owns composite pause:
verificationPausedRef.current = mobileCheckoutPauses || isListPickerOpen || isAutoDetectPaused || Boolean(identification.disambiguation);Desktop Auto-detect toggle sets
isAutoDetectPaused(OFF = paused). -
Focused card state:
focusedCardId+ derivedfocusedCard. On new scan, auto-focus latest unprocessed (scannedCards.find(c => !c.processed)). -
Inspector add success: wrap
addSingleCardToOwned— on success showScannerToast(Added to My Collection), advance to next unprocessed or empty. Boot finding: currentaddSingleCardToOwneddoes not return boolean — checkmarkCardAsProcessedby awaiting and verifying card.processed or catch errors; optionally extend queue method toreturn true/falsein a follow-up if needed inside this brief only ifuse-scanner-queue.jsis NOT in scope — use try/catch around API and inspect state after await.Note: To return success reliably, add minimal change to
lib/use-scanner-queue.jsaddSingleCardToOwned→return trueon success,return falseon error — only if needed; prefer not to expand files list. Alternative: duplicate add call in page usingaddScannedCardToOwnedfromscanner-route-api.js— rejected; instead addreturn true/falsetouse-scanner-queue.jsis out of files — useaddingCardIdsclearing + no error log as success heuristic OR adduse-scanner-queue.jsto this brief's files.Architect lock: add
lib/use-scanner-queue.jsto this brief foraddSingleCardToOwnedreturningboolean. -
List picker desktop:
handleListPickuses focused card id single selection (queue.addSingleCardToCollection(focusedCard, collectionId)), not bulk selected set. -
Leave modal: keep existing; trigger on back + unprocessed queue.
-
Batch Scan: hidden
<input type="file" multiple accept="image/*" />on desk bar;runSequentialGalleryIdentifywithcancelRef; ononFileErrorenqueue synthetic failed row viaqueue.handleCardScannedwithidentifyFailed: trueOR dedicatedenqueueFailedIdentify(file, error)inline in page. -
Rescan: clear identify fields on focused card (
updateCardMetadata), setprocessed: false; ifscanImageUrlpresent fetch blob andidentifyFromGalleryFile; else toast Rescan from camera. -
ScannerCamera workstation: add
variant="default" | "workstation"(orlayoutMode).At
md+withvariant="workstation":- Container: framed panel not
min-h-[100dvh]full-bleed — usemd:rounded-xl,md:aspect-video,md:min-h-0, outerGlassSurface+ ember rim. - Hide overlay top bar + bottom bar (
max-md:keep current). - Hide
ScannerScanPeek, mobile LIVE badge,ScannerCountPillatmd+. - Show Auto-detect badge top-left in frame (ON/OFF from
!verificationPausedRefcomposite for auto-detect slice only — passautoDetectOnprop). - Badge dot pulse when ON;
prefers-reduced-motion: reduce→ static dot. - Accept optional
deskControlsslot rendered below frame by page ORdeskControlBarprop — page renders Upload/Batch/Auto-detect/Camera select below.
- Container: framed panel not
-
Desk control bar (page, md+): Camera
<select>+ Upload Image + Batch Scan + Auto-detect switch per Design direction. Upload uses existing single-file input path. -
Tests:
test/pages/scanner.test.js— mock hooks; assert mobile checkout sheet present at narrow width; workstation elements (ScannerResultPanel, strip) atmd+(usewindow.matchMediamock).test/components/ScannerCamera.test.js— overlay chrome hidden at workstation variant. -
Visual-diff:
/scanneris not intests/visual/(onlyhome.png) — no baseline update required.
use-scanner-queue.js changes
addSingleCardToOwnedreturnstrueon success,falseon error (aftertry/catch).addSingleCardToCollectionsame boolean return for desktop list picker.
Acceptance criteria
md+workstation grid: header + camera column + inspector + bottom stripmax-mdimmersive checkout sheet unchanged (regression test)chromedefault on desktop, immersive on mobile- Device picker + Batch Scan + Auto-detect on desk bar only at
md+ ScannerTipsin desktop header- Inspector advance + toast on successful add
- Batch sequential with cancel + per-file failure flags
verificationPausedRefcomposite pause without identification hook overwrite- No forbidden copy strings
- tests added; no visual-diff baseline change
- no scope expansion beyond listed files
Rationale (≤3 sentences)
Only one brief can own pages/scanner.js composition. Camera workstation variant and pause fix are tightly coupled to page wiring. Briefs 1–5 ship mountable units; this brief integrates them without blocking parallel implementers.