feat(scanner): persist scan captures to Blob (Brief 3) #44
No reviewers
Labels
No labels
agent-context-drift
bug
documentation
duplicate
enhancement
good first issue
help wanted
invalid
question
wontfix
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference: rstillwell/deckhearth#44
Loading…
Reference in a new issue
No description provided.
Delete branch "feat/redesign-scanner-flow-brief-3"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Summary
POST /api/scan/upload-image(auth + upload rate limit) to store confirmed scan frames in Vercel Blob atscans/{userId}/{uuid}.jpgCameraScanneruploads the capture on confirm and passesscanImageUrlthrough to the scanner queueuser_cards.scan_image_url;POST /api/user-cardsaccepts and persists it when adding to owned cardsCompletes the
redesign-scanner-flowconvoy (Briefs 1–3).Test plan
npm run migrate uplocally (addsscan_image_urlcolumn)user_cardshasscan_image_urlMade with Cursor
The latest updates on your projects. Learn more about Vercel for GitHub.
Pipeline Health
Build + CI gates
Build runs on Vercel; this CI runs lint and schema-map drift only (no duplicate build).
Role reports
See individual comments above for details. This rollup updates automatically.
Visual Diff
Screenshots and diffs uploaded as artifacts: view run
If intentional changes: update snapshots locally with
npx playwright test --project=visual --update-snapshotsand commit.Reviewer Report
condition/is_foilsilently dropped for collection and deck destinations (see Finding 1)upload-image.jsis auth-gated, rate-limited (checkUploadRateLimit), validates MIME type via regex, enforces 5 MB buffer capemitScannedCardmade async in a scan pipeline;handleCardScannedreadsscannedCardsoutsidesetStatecallback (stale-closure risk on rapid scans); heavy scanner.js rewriteScannedCardItem,ScannerDestinationPicker, orupload-image.jsdocs/SCHEMA_MAP.mdupdated withscan_image_url; migration file self-documentsFindings
🟡 AC #2 partially satisfied —
condition/is_foilsilently dropped for collection and deck destinations. Bothcollections/[identifier]/cards.jsanddecks/[id]/cards.jsnow acceptconditionandis_foilin the request body, but neithercollection_cardsnordeck_cardshas those columns. For collections, the values are logged to activity only (not in the SQLUPDATE/INSERT). For decks, the values are echoed in the responsemetadataobject only (not stored). Onlyuser_cardsactually persists them. If condition/foil-per-collection-slot is a product goal, a follow-up migration is needed; if it's intentional (card-level metadata lives onuser_cards), document it and strip the unused params from the two handlers to avoid misleading callers. Suggested convoy:add-condition-foil-to-collection-deck-schemaor a doc note in AGENTS.md.🟡
user-cards.jsmissing theparseInt/NaN guard added in the decks handler.decks/[id]/cards.jsnow validatesquantitywithparseInt(quantity, 10)+isNaNcheck.user-cards.jsstill adds rawquantitydirectly to the existing row's count. Suggested follow-up: add parseInt/NaN guard touser-cards.js, matching the decks handler.🟢
scan_image_urlstored without origin validation. Low risk today; worth an origin prefix check in a future hardening pass.🟢 No unit tests for new surfaces. Suggested convoy:
test-scanner-redesign-surfaces.🟢 Stale-closure risk in
handleCardScannedon rapid-fire scans. Fixable with auseRefqueue or moving dedup inside thesetScannedCardsupdater.Approval recommendation
main; findings should be captured as follow-up convoy tasks rather than reverts)Design System Audit
Token violations
All violations are in
components/CameraScanner.js(pre-existing overlay chrome, not introduced by Briefs 1–3):borderColoruses hardcoded#10B981/#3B82F6. Use semantic CSS variables or Tailwind border classes.boxShadowuses 8-digit hex literals#10B98150/#3B82F650.backgroundColorrepeats the same hex literals.backgroundColor: rgba(239, 68, 68, 0.9)→ usebg-red-500/90.border: 3px solid rgba(255, 255, 255, 0.3)→border-white/30.border: 4px solid rgba(255, 255, 255, 0.2)→border-white/20.Duplicate primitives
CollectionSelectionModal.js/ShareModal.jsas modal shells. Flag for a shared modal wrapper convoy.Other findings
ScannedCardItem.js,ScannerDestinationPicker.js,pages/scanner.js— Clean. All colors viavar(--*)tokens; spacing via Tailwind utilities.bg-black bg-opacity-*(pre-existing pattern across scanner modals).A11y Audit
Critical (must fix)
ScannedCardItem.js:115—aria-labelon bare<div>is dropped by AT; ownership badge inaccessible — addrole="status".ScannedCardItem.js:226— Remove button is emoji-only — addaria-label="Remove card".ScannedCardItem.js:236— Collection<select>unlabeled — addaria-label.ScannedCardItem.js:259— Deck<select>unlabeled — addaria-label.CameraScanner.js:941— Start camera button is icon-only — addaria-label="Start camera".CameraScanner.js:903— Stop camera button is icon-only — addaria-label="Stop camera".CameraScanner.js:1027— Disambiguation dialog missing focus trap / restore on close.pages/scanner.js:716— Create Collection modal missingrole="dialog",aria-modal, focus trap.Warnings (recommended)
aria-labels (scanner.js:651,672,699).h4after pageh2).<video>feed needsaria-label="Card scanner camera feed".<ul>/<li>).aria-live="polite".Notes
ScannerDestinationPicker.jsis clean — native buttons witharia-pressed, fieldset/legend, labeled selects. UX constraint "destination picker keyboard-operable" ✅.scanNotice/autoRouteErrorlive regions are correctly implemented.