100 lines
4 KiB
Markdown
100 lines
4 KiB
Markdown
|
|
---
|
||
|
|
convoy: scanner-mobile-checkout
|
||
|
|
brief_number: 2
|
||
|
|
depends_on: []
|
||
|
|
recommended_model: composer-2.5-fast
|
||
|
|
model_tier: fast
|
||
|
|
files:
|
||
|
|
- lib/use-scanner-queue.js
|
||
|
|
- lib/scanner-session.js
|
||
|
|
- test/lib/scanner-session.test.js
|
||
|
|
- test/lib/use-scanner-queue.test.js
|
||
|
|
cross_brief_commitments:
|
||
|
|
- brief: 4
|
||
|
|
description: |
|
||
|
|
Export `clearScannerCartStorage()` from `scanner-session.js`. Brief 4
|
||
|
|
calls it when the user confirms "Leave" on the back-guard modal (D1/D7).
|
||
|
|
Queue hook exposes `hydrateFromStorage` on mount — Brief 4 does not
|
||
|
|
reimplement hydration.
|
||
|
|
---
|
||
|
|
|
||
|
|
# Brief 2: Cart model — stop auto-route + sessionStorage
|
||
|
|
|
||
|
|
## Goal (1 sentence)
|
||
|
|
|
||
|
|
Reverse rebuild D3 auto-route: identifies enqueue locally only, auto-select new rows, and persist cart + selection in `sessionStorage` (D7).
|
||
|
|
|
||
|
|
## Files in scope (do not edit anything else)
|
||
|
|
|
||
|
|
- `lib/use-scanner-queue.js`
|
||
|
|
- `lib/scanner-session.js`
|
||
|
|
- `test/lib/scanner-session.test.js`
|
||
|
|
- `test/lib/use-scanner-queue.test.js` (new)
|
||
|
|
|
||
|
|
## Conventions to follow
|
||
|
|
|
||
|
|
- **No new API routes.** Commit paths stay `addScannedCardToOwned` / `addScannedCardToCollection` from `lib/scanner-route-api.js` (already verified).
|
||
|
|
- **Do not use** `SCANNER_SESSION_STORAGE_KEY` / `localStorage` for cart rows (UX anti-pattern). Add a separate key in `scanner-session.js`:
|
||
|
|
|
||
|
|
```js
|
||
|
|
export const SCANNER_CART_STORAGE_KEY = 'deckhearth:scanner-cart';
|
||
|
|
```
|
||
|
|
|
||
|
|
- Serialize `selectedCards` as `number[]` (Set is not JSON-safe).
|
||
|
|
- Keep `mergeScannedCardEntry` unchanged — cart entry shape already includes `processed: false` and `confidence` from `buildScannedCardPayload`.
|
||
|
|
- `sessionDestination` param may remain on the hook signature for backward compat but **must not** trigger network I/O in `handleCardScanned`.
|
||
|
|
|
||
|
|
## Implementation shape (verified against `use-scanner-queue.js`)
|
||
|
|
|
||
|
|
**Remove auto-route** — delete the block after `setScannedCards(nextQueue)`:
|
||
|
|
|
||
|
|
```js
|
||
|
|
// DELETE lines ~148-168 (sessionDestination guard + routeScannedCardToDestination)
|
||
|
|
```
|
||
|
|
|
||
|
|
**Replace `handleCardScanned` with enqueue-only:**
|
||
|
|
|
||
|
|
```js
|
||
|
|
const handleCardScanned = (cardData) => {
|
||
|
|
setAutoRouteError(null);
|
||
|
|
const { cardEntry, scannedCards: nextQueue } = mergeScannedCardEntry(
|
||
|
|
scannedCards,
|
||
|
|
cardData,
|
||
|
|
scanDefaults
|
||
|
|
);
|
||
|
|
setScannedCards(nextQueue);
|
||
|
|
setSelectedCards((prev) => new Set([...prev, cardEntry.id]));
|
||
|
|
persistCart(nextQueue, new Set([...selectedCards, cardEntry.id]));
|
||
|
|
};
|
||
|
|
```
|
||
|
|
|
||
|
|
Add `loadScannerCart()` / `saveScannerCart({ scannedCards, selectedCardIds })` / `clearScannerCartStorage()` in `scanner-session.js`.
|
||
|
|
|
||
|
|
Hydrate on mount in `useScannerQueue` via `useState` initializer + `useEffect` debounced save on `[scannedCards, selectedCards]` changes.
|
||
|
|
|
||
|
|
**Checkout helpers** (used by Brief 4 footer CTAs):
|
||
|
|
|
||
|
|
```js
|
||
|
|
const commitSelectedToOwned = async () => handleBulkAction('owned');
|
||
|
|
const commitSelectedToCollection = async (collectionId) =>
|
||
|
|
handleBulkAction('collection', collectionId);
|
||
|
|
```
|
||
|
|
|
||
|
|
Export these plus `unprocessedCount` derived helper: `scannedCards.filter(c => !c.processed).length`.
|
||
|
|
|
||
|
|
After successful `handleBulkAction`, remove committed rows from `scannedCards` entirely (not `processed: true`) per D1/D6 — cart holds only uncommitted items.
|
||
|
|
|
||
|
|
## Acceptance criteria
|
||
|
|
|
||
|
|
- [ ] `handleCardScanned` performs zero `fetch` calls (no `routeScannedCardToDestination`)
|
||
|
|
- [ ] New enqueue auto-adds card `id` to `selectedCards`
|
||
|
|
- [ ] Cart + selection survive `sessionStorage` round-trip within a tab (`test/lib/scanner-session.test.js`)
|
||
|
|
- [ ] `test/lib/use-scanner-queue.test.js`: mock `scanner-route-api` and assert `handleCardScanned` never calls route helpers; assert `handleBulkAction('owned')` calls `addScannedCardToOwned` once per selected card
|
||
|
|
- [ ] `clearScannerCartStorage()` clears persisted state
|
||
|
|
- [ ] Existing `test/lib/scanner-session.test.js` cases still pass
|
||
|
|
- [ ] No scope expansion
|
||
|
|
|
||
|
|
## Rationale (≤3 sentences)
|
||
|
|
|
||
|
|
Cart semantics are pure client state and can land before any UI work. Separating sessionStorage from the existing `localStorage` destination prefs avoids D7/localStorage anti-pattern. Removing committed rows instead of flipping `processed` simplifies the checkout sheet list.
|