diff --git a/.convoys/scanner-mobile-checkout.md b/.convoys/scanner-mobile-checkout.md
index 9686245..3299157 100644
--- a/.convoys/scanner-mobile-checkout.md
+++ b/.convoys/scanner-mobile-checkout.md
@@ -7,7 +7,7 @@ success_metric: |
the user can open a checkout sheet, select cards, and commit them
to My Collection or a List without leaving the session.
skip: []
-status: open
+status: shipped
created: 2026-08-14
depends_on:
- scanner-rebuild
diff --git a/components/CameraScanner.js b/components/CameraScanner.js
deleted file mode 100644
index 885018a..0000000
--- a/components/CameraScanner.js
+++ /dev/null
@@ -1,44 +0,0 @@
-import { useRef } from 'react';
-import { useCameraScanner } from '../lib/use-camera-scanner.js';
-import { useScannerIdentification } from '../lib/use-scanner-identification.js';
-import CameraScannerView from './CameraScannerView.js';
-
-export default function CameraScanner({ onCardScanned, onError }) {
- const verificationPausedRef = useRef(false);
- const onVerifyCardRef = useRef(() => {});
-
- const camera = useCameraScanner({
- onError,
- verificationPausedRef,
- onVerifyCard: (card) => onVerifyCardRef.current(card),
- });
-
- const identification = useScannerIdentification({
- onCardScanned,
- onError,
- videoRef: camera.videoRef,
- canvasRef: camera.canvasRef,
- onVerifyCardRef,
- verificationPausedRef,
- });
-
- return (
-
- );
-}
diff --git a/components/CameraScannerView.js b/components/CameraScannerView.js
deleted file mode 100644
index 0ff38a1..0000000
--- a/components/CameraScannerView.js
+++ /dev/null
@@ -1,251 +0,0 @@
-import ScanDisambiguationDialog from './ScanDisambiguationDialog.js';
-
-const CONFIRMED_COLOR = '#10B981';
-const SCANNED_COLOR = '#3B82F6';
-
-function overlayBorderColor(status) {
- if (status === 'scanned') return SCANNED_COLOR;
- return CONFIRMED_COLOR;
-}
-
-function overlayGlowColor(status) {
- if (status === 'scanned') return `${SCANNED_COLOR}50`;
- return `${CONFIRMED_COLOR}50`;
-}
-
-export default function CameraScannerView({
- videoRef,
- canvasRef,
- detectionCanvasRef,
- isStreaming,
- isDetecting,
- trackedCards,
- videoMetrics,
- startCamera,
- stopCamera,
- scanNotice,
- disambiguation,
- submittingReview,
- onPick,
- onNotInCatalog,
- onCancelDisambiguation,
-}) {
- const foundCards = trackedCards.filter(
- (card) => card.status === 'confirmed' || card.status === 'scanned'
- );
- const foundCardCount = foundCards.length;
-
- return (
-
- {scanNotice && (
-
- {scanNotice}
-
- )}
-
-
-
- {isStreaming && videoMetrics.width > 0 && videoMetrics.height > 0 && foundCards.map((card) => (
-
-
- {card.status === 'scanned' ? 'πΈ Scanned' : 'β
Card Found'}
-
-
- ))}
-
- {isStreaming && (
-
- {isDetecting && (
-
-
-
- π― Scanning {foundCardCount > 0 && `(${foundCardCount} found)`}
-
-
- )}
-
-
-
-
-
- )}
-
- {isStreaming && (
-
- )}
-
- {!isStreaming && (
-
-
-
-
-
- Camera Ready
-
-
- Position trading cards in view for smart detection
-
-
-
-
-
-
-
- )}
-
-
-
-
-
- {isStreaming && (
-
-
-
- π―
-
-
- Smart Detection Active
-
-
-
-
- β
- Shape recognition
-
-
- β
- Server identification
-
-
- β
- Position tracking
-
-
- β
- Database lookup
-
-
-
- )}
-
-
-
- );
-}
diff --git a/components/ScannerPageView.js b/components/ScannerPageView.js
deleted file mode 100644
index 3559730..0000000
--- a/components/ScannerPageView.js
+++ /dev/null
@@ -1,327 +0,0 @@
-import CameraScanner from './CameraScanner';
-import ScannerDestinationPicker from './ScannerDestinationPicker';
-import ScannedCardItem, { CONDITION_OPTIONS } from './ScannedCardItem';
-import OCRSettings from './OCRSettings';
-import { Modal, Input, Button } from './ui';
-import { VOCAB } from '../lib/collection-vocabulary.js';
-
-export default function ScannerPageView({
- gameFilter,
- onGameFilterChange,
- sessionDestination,
- onDestinationChange,
- scanDefaults,
- onScanDefaultsChange,
- queue,
- showOCRSettings,
- onOpenOCRSettings,
- onCloseOCRSettings,
- onScannerError,
-}) {
-
- return (
-
-
-
- π Card Scanner
-
-
- Pick a destination once β every scan lands there until you change it
-
-
-
-
-
- {queue.autoRouteError && (
-
- {queue.autoRouteError}
-
- )}
-
-
-
-
- Defaults for new scans
-
-
- Applied to each card when it enters the queue
-
-
-
-
-
-
-
-
-
-
-
- Camera Scanner
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Scanned Cards
-
-
-
- {queue.scannedCards.length} cards
-
- {queue.scannedCards.length > 0 && (
-
- )}
-
-
-
-
- {queue.scannedCards.length === 0 ? (
-
-
π±
-
No cards scanned yet
-
Start scanning to see cards here
-
- ) : (
-
- {queue.scannedCards.map((card) => (
- -
- queue.toggleCardSelection(card.id)}
- onIncrement={() => queue.incrementCardQuantity(card.id)}
- onDecrement={() => queue.decrementCardQuantity(card.id)}
- onUpdateMetadata={(patch) => queue.updateCardMetadata(card.id, patch)}
- onMarkOwned={() => queue.addSingleCardToOwned(card)}
- onAddToCollection={(collectionId) => queue.addSingleCardToCollection(card, collectionId)}
- onAddToDeck={(deckId) => queue.addSingleCardToDeck(card, deckId)}
- onRemove={() => queue.removeScannedCard(card.id)}
- isAdding={queue.addingCardIds.has(card.id)}
- />
-
- ))}
-
- )}
-
-
-
-
-
- {queue.selectedCards.size > 0 && (
-
-
-
-
- {queue.selectedCards.size}
-
-
- {queue.selectedCards.size === 1 ? 'card selected' : 'cards selected'}
-
-
-
-
-
-
-
-
- {queue.collections.length > 0 && (
-
- )}
-
- {queue.decks.length > 0 && (
-
- )}
-
-
-
-
-
-
-
- )}
-
-
queue.setShowCreateCollection(false)}
- title="Create New List"
- size="md"
- >
- queue.setNewCollectionName(e.target.value)}
- onKeyPress={(e) => {
- if (e.key === 'Enter') {
- queue.createCollection();
- }
- }}
- className="mb-4"
- />
-
-
-
-
-
-
- {showOCRSettings &&
}
-
- );
-}
diff --git a/components/scanner/DeckModeIndicator.js b/components/scanner/DeckModeIndicator.js
deleted file mode 100644
index 34b1921..0000000
--- a/components/scanner/DeckModeIndicator.js
+++ /dev/null
@@ -1,51 +0,0 @@
-export default function DeckModeIndicator({ current, target, game }) {
- const progress = target > 0 ? Math.min(current / target, 1) : 0;
- const isComplete = current >= target;
-
- return (
-
- {/* Progress track */}
-
-
- {/* Labels */}
-
-
- {isComplete ? 'Deck complete!' : `${current}/${target} cards`}
-
-
- {game}
-
-
-
- );
-}
diff --git a/components/scanner/ReviewCardItem.js b/components/scanner/ReviewCardItem.js
index 521742d..0d94186 100644
--- a/components/scanner/ReviewCardItem.js
+++ b/components/scanner/ReviewCardItem.js
@@ -1,6 +1,7 @@
/* eslint-disable @next/next/no-img-element -- External card image URLs; next/image migration is out of scope. */
import { useEffect, useRef, useState } from 'react';
import { formatProcessedDestination, VOCAB } from '../../lib/collection-vocabulary.js';
+import GlassSurface from '../ui/GlassSurface.js';
const CONDITION_OPTIONS = ['NM', 'LP', 'MP', 'HP', 'DMG'];
@@ -61,9 +62,11 @@ export default function ReviewCardItem({
return (
@@ -158,10 +161,14 @@ export default function ReviewCardItem({
{menuOpen && (
-
-
+
)}
) : !card.processed ? (
diff --git a/components/scanner/ScannerCheckoutSheet.js b/components/scanner/ScannerCheckoutSheet.js
index d99a252..dc0083c 100644
--- a/components/scanner/ScannerCheckoutSheet.js
+++ b/components/scanner/ScannerCheckoutSheet.js
@@ -2,6 +2,7 @@ import { useCallback, useEffect, useRef, useState } from 'react';
import { useFocusTrap } from '../../lib/use-focus-trap';
import { VOCAB } from '../../lib/collection-vocabulary.js';
import { Button } from '../ui';
+import GlassSurface from '../ui/GlassSurface.js';
import ReviewCardItem from './ReviewCardItem';
function confidencePercent(confidence) {
@@ -20,8 +21,9 @@ function CheckoutFooter({
return (