deckhearth/.convoys/scanner-mobile-checkout/brief-2-cart-model-session-storage.md

100 lines
4 KiB
Markdown
Raw Permalink Normal View History

---
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.