deckhearth/.convoys/scanner-desktop-layout/brief-6-page-workstation-wiring.md
varutasu 938c161a26
feat(scanner): add desktop workstation layout (#165)
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>
2026-08-15 17:21:23 -05:00

7.5 KiB
Raw Blame History

convoy brief_number depends_on recommended_model model_tier files cross_brief_commitments
scanner-desktop-layout 6
1
2
3
4
5
composer-2.5-fast fast
pages/scanner.js
components/scanner/ScannerCamera.js
lib/use-scanner-identification.js
lib/use-scanner-queue.js
test/pages/scanner.test.js
test/components/ScannerCamera.test.js
brief description
1 Wires `camera.videoDevices`, `selectedDeviceId`, `setSelectedDeviceId`, and `devicePickerStatus` into the desk Camera `<select>` (md+ only).
brief description
2 Mounts `<ScannerTips />` in desktop page header (`hidden md:flex` row).
brief description
3 Mounts `<ScannerResultPanel />` with focus/advance/toast orchestration after `addSingleCardToOwned`.
brief description
4 Mounts `<ScannerHistoryStrip />` with `focusedCardId`, batch progress, and bulk commit handlers.
brief description
5 Batch Scan multi-file input calls `runSequentialGalleryIdentify` with `identification.identifyFromGalleryFile`.

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.js
  • components/scanner/ScannerCamera.js
  • lib/use-scanner-identification.js
  • lib/use-scanner-queue.js
  • test/pages/scanner.test.js
  • test/components/ScannerCamera.test.js

Conventions to follow

  • Layout chrome (boot finding): chrome="immersive" already shows sidebar + TopSearchBar at md+ per Layout.js (max-md:hidden only 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.js edits.

  • Mobile zero regression: keep ScannerCheckoutSheet, overlay camera chrome, ScannerCountPill, scan peek — all md:hidden or max-md: as today.

  • Remove desktop ScannerReview side panel (hidden md:flex block) — replaced by inspector + strip.

  • Pause coordination (boot finding): useScannerIdentification currently 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 + derived focusedCard. On new scan, auto-focus latest unprocessed (scannedCards.find(c => !c.processed)).

  • Inspector add success: wrap addSingleCardToOwned — on success show ScannerToast (Added to My Collection), advance to next unprocessed or empty. Boot finding: current addSingleCardToOwned does not return boolean — check markCardAsProcessed by awaiting and verifying card.processed or catch errors; optionally extend queue method to return true/false in a follow-up if needed inside this brief only if use-scanner-queue.js is 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.js addSingleCardToOwnedreturn true on success, return false on error — only if needed; prefer not to expand files list. Alternative: duplicate add call in page using addScannedCardToOwned from scanner-route-api.jsrejected; instead add return true/false to use-scanner-queue.js is out of files — use addingCardIds clearing + no error log as success heuristic OR add use-scanner-queue.js to this brief's files.

    Architect lock: add lib/use-scanner-queue.js to this brief for addSingleCardToOwned returning boolean.

  • List picker desktop: handleListPick uses 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; runSequentialGalleryIdentify with cancelRef; on onFileError enqueue synthetic failed row via queue.handleCardScanned with identifyFailed: true OR dedicated enqueueFailedIdentify(file, error) inline in page.

  • Rescan: clear identify fields on focused card (updateCardMetadata), set processed: false; if scanImageUrl present fetch blob and identifyFromGalleryFile; else toast Rescan from camera.

  • ScannerCamera workstation: add variant="default" | "workstation" (or layoutMode).

    At md+ with variant="workstation":

    • Container: framed panel not min-h-[100dvh] full-bleed — use md:rounded-xl, md:aspect-video, md:min-h-0, outer GlassSurface + ember rim.
    • Hide overlay top bar + bottom bar (max-md: keep current).
    • Hide ScannerScanPeek, mobile LIVE badge, ScannerCountPill at md+.
    • Show Auto-detect badge top-left in frame (ON/OFF from !verificationPausedRef composite for auto-detect slice only — pass autoDetectOn prop).
    • Badge dot pulse when ON; prefers-reduced-motion: reduce → static dot.
    • Accept optional deskControls slot rendered below frame by page OR deskControlBar prop — page renders Upload/Batch/Auto-detect/Camera select below.
  • 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) at md+ (use window.matchMedia mock). test/components/ScannerCamera.test.js — overlay chrome hidden at workstation variant.

  • Visual-diff: /scanner is not in tests/visual/ (only home.png) — no baseline update required.

use-scanner-queue.js changes

  • addSingleCardToOwned returns true on success, false on error (after try/catch).
  • addSingleCardToCollection same boolean return for desktop list picker.

Acceptance criteria

  • md+ workstation grid: header + camera column + inspector + bottom strip
  • max-md immersive checkout sheet unchanged (regression test)
  • chrome default on desktop, immersive on mobile
  • Device picker + Batch Scan + Auto-detect on desk bar only at md+
  • ScannerTips in desktop header
  • Inspector advance + toast on successful add
  • Batch sequential with cancel + per-file failure flags
  • verificationPausedRef composite 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 15 ship mountable units; this brief integrates them without blocking parallel implementers.