feat(scanner): add debug instrumentation for vision pipeline timing #155

Merged
rstillwell merged 31 commits from feat/scanner-debug-mode into main 2026-09-01 18:11:03 -04:00
Owner

Summary

Adds real-time performance logging to the card scanner pipeline for diagnosing slow identification on iOS Chrome and mobile devices.

Changes

  • lib/scanner-card-identify.js — Instrumented Layer 0 (pgvector visual similarity), Layer 1 (Tesseract OCR + pg_trgm), and Layer 2 (Vision API) with per-layer timing logs. Pipeline orchestration logs total scan time and each layer transition.
  • lib/use-scanner-identification.js — Added shutter-press logging with attempt count, verification outcome timing, rate-limit cooldown display, and error-path instrumentation.
  • docs/SCANNER_DEBUG_MODE.md — Full usage guide with activation instructions, log format reference, common patterns (fast L0 hit, OCR path, vision API path, rate limit), and iOS Chrome debugging tips.

How to use

// In browser console (iOS Chrome, Mac Safari, etc.)
localStorage.setItem("SCANNER_DEBUG", "true")

Then navigate to /scanner and scan cards. Timestamped logs will appear in the console showing which pipeline layer is slow.

What gets logged

  • Shutter press with tracker ID and attempt number
  • Layer 0 (pgvector) timing and escalate/resolved status
  • Layer 1 (OCR) timing with extracted text and confidence
  • Layer 2 (Vision API) timing with response summary
  • Rate limit cooldown timestamps when 15/min is exhausted
  • Pipeline total for each card verification

Zero overhead when off

All logging is gated by isDebugMode() which checks window.__SCANNER_DEBUG or localStorage — no performance impact in production.

Test results

All 58 scanner-specific tests pass (3.23s). No behavioral changes — purely additive instrumentation.

## Summary Adds real-time performance logging to the card scanner pipeline for diagnosing slow identification on iOS Chrome and mobile devices. ### Changes - **`lib/scanner-card-identify.js`** — Instrumented Layer 0 (pgvector visual similarity), Layer 1 (Tesseract OCR + pg_trgm), and Layer 2 (Vision API) with per-layer timing logs. Pipeline orchestration logs total scan time and each layer transition. - **`lib/use-scanner-identification.js`** — Added shutter-press logging with attempt count, verification outcome timing, rate-limit cooldown display, and error-path instrumentation. - **`docs/SCANNER_DEBUG_MODE.md`** — Full usage guide with activation instructions, log format reference, common patterns (fast L0 hit, OCR path, vision API path, rate limit), and iOS Chrome debugging tips. ### How to use ```javascript // In browser console (iOS Chrome, Mac Safari, etc.) localStorage.setItem("SCANNER_DEBUG", "true") ``` Then navigate to `/scanner` and scan cards. Timestamped logs will appear in the console showing which pipeline layer is slow. ### What gets logged - **Shutter press** with tracker ID and attempt number - **Layer 0** (pgvector) timing and escalate/resolved status - **Layer 1** (OCR) timing with extracted text and confidence - **Layer 2** (Vision API) timing with response summary - **Rate limit** cooldown timestamps when 15/min is exhausted - **Pipeline total** for each card verification ### Zero overhead when off All logging is gated by `isDebugMode()` which checks `window.__SCANNER_DEBUG` or `localStorage` — no performance impact in production. ### Test results All 58 scanner-specific tests pass (3.23s). No behavioral changes — purely additive instrumentation.
rstillwell added 31 commits 2026-09-01 18:09:29 -04:00
* Sync agent pipeline artifacts to 0.6.0.

Add model routing defaults, L2 role updates, convoy telemetry, and manifest tracking without touching unrelated in-progress work.

* Record tcg-vault interactive sync (kept local L1/L3 customizations).

Update last_synced_at after reviewing behind/conflict artifacts; no overwrites applied.

Co-authored-by: Cursor <cursoragent@cursor.com>

* chore(agent-pipeline): sync 0.6.0 — implementer Mode 2 fix pass + UI designer

- role-implementer Mode 2 (fix pass) and role-reviewer hand-off
- role-ui-designer + model-routing updates from upstream 783e2a3
- manifest last_synced_at bumped

Co-authored-by: Cursor <cursoragent@cursor.com>

* chore(agent-pipeline): sync v0.7.0 model routing

Composer Standard for architect, Grok for audit fan-out, Sonnet 5 escalation path.

Co-authored-by: Cursor <cursoragent@cursor.com>

* chore(agent-pipeline): finish 0.6.0/0.7.0 sync on branch

Install security baseline, convoy planning, security-audit and ui-ux-pro-max
skills; align PR template and metrics docs with convoy-metrics-gate; refresh
manifest hashes while keeping tcg-vault-local L1/L3 customizations.

Co-authored-by: Cursor <cursoragent@cursor.com>

---------

Co-authored-by: Cursor <cursoragent@cursor.com>
* Start scanner-mobile-checkout convoy for the cart-then-commit phone flow.

Co-authored-by: Cursor <cursoragent@cursor.com>

* Ship a cart-then-commit mobile scanner so phone sessions stay on the camera.

Scan matches enqueue locally instead of auto-writing ownership, checkout happens in a sheet, and audit fixes cover stale commit detection, returnUrl open redirects, nested Escape, and ember detection chrome.

Co-authored-by: Cursor <cursoragent@cursor.com>

---------

Co-authored-by: Cursor <cursoragent@cursor.com>
* docs(convoy): seed scanner identify upgrade epic and sub-convoys

Baseline scan_attempts telemetry and three-phase plan for faster, more
accurate card identification without touching scanner chrome.

Co-authored-by: Cursor <cursoragent@cursor.com>

* feat(scanner): tighten Layer-1 identify hot path (Phase 1)

Cut verify hold-still gates, OCR collector numbers on Layer 1, request
structured Gemini JSON, and skip automatic L2 refine when L1 opens the
printing picker. Includes convoy UX/architecture briefs and unit tests.

Co-authored-by: Cursor <cursoragent@cursor.com>

---------

Co-authored-by: Cursor <cursoragent@cursor.com>
Add quad corner refinement and homography warp for rectified identify
crops; wire warped capture through identifyTrackedCardCapture with
validation and axis-aligned fallback; add detection frame a11y labels.

Co-authored-by: Cursor <cursoragent@cursor.com>
B3 ran before the B2 column add on a fresh database. Move the index to a later migration so collaboration tables still apply and the index lands after visibility is present.

Co-authored-by: Cursor <cursoragent@cursor.com>
Align toast, sheet, and Review N pill with GlassSurface tokens, use solid cart rows to avoid stacked blurs, and delete pre-rebuild scanner components no longer referenced by /scanner.

Co-authored-by: Cursor <cursoragent@cursor.com>
Add pgvector embeddings on cards, server-side cohere/embed-v4.0 via AI
Gateway, kNN identify route, and L0→L1→L2 client orchestration with
empty-index fast escalate and id-cursor backfill job.

Co-authored-by: Cursor <cursoragent@cursor.com>
Add lib/card-embed.js to the server-only LLM allowlist (forbidden-patterns
Check 3). Make the pgvector migration degrade gracefully when CT 102 CI
cannot CREATE EXTENSION vector so migrate up still passes.

Co-authored-by: Cursor <cursoragent@cursor.com>
Record Neon migration applied, scan_attempts snapshot, and backfill gate.

Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(ci): allow card-embed.js and skip pgvector on non-superuser CI

Add lib/card-embed.js to the server-only LLM allowlist (forbidden-patterns
Check 3). Make the pgvector migration degrade gracefully when CT 102 CI
cannot CREATE EXTENSION vector so migrate up still passes.

Co-authored-by: Cursor <cursoragent@cursor.com>

* docs(convoy): post-ship metrics and operator checklist for Phase 3

Record Neon migration applied, scan_attempts snapshot, and backfill gate.

Co-authored-by: Cursor <cursoragent@cursor.com>

---------

Co-authored-by: Cursor <cursoragent@cursor.com>
Replace @vercel/postgres, Blob, and Upstash with lib/sql.js, MinIO object
storage, and CT 102 Redis rate limits. Add Dockerfile for Dokploy deploy,
homelab runbooks, Neon data-copy helper, and point CI smoke/visual at the
homelab URL instead of Vercel previews.

Co-authored-by: Cursor <cursoragent@cursor.com>
Homelab migration merged to main; Dokploy should track production there.

Co-authored-by: Cursor <cursoragent@cursor.com>
Record Aug 15 measurement showing Phases 1–2 missed success targets, Phase 3 code is live with 15% embedding backfill and zero L0 traffic yet.

Co-authored-by: Cursor <cursoragent@cursor.com>
Ships Brief 7 (documentation + verification runbook) of the
reconcile-historical-add-scripts convoy, ~2 months post-hoc. The
6 implementer briefs (B1-B6) landed 2026-06-14 to 2026-07-06 via
PRs #148, #149, #150, #151, #152, #153. This PR closes the loop:

- Brings the architect's parent convoy file + 6 brief files onto
  main (they only existed on the stale convoy/reconcile-historical-
  add-scripts branch, never merged)
- Adds § As-shipped to the parent convoy file documenting all 6
  squash SHAs + PR numbers + merge dates + the reservation-timestamp
  rename (1781000000001-006 → 1781442330001-006 in ec9bb2b, except
  B3 which kept its original) + the B6 shipped-as-tiny-migration
  deviation from the collapse-to-docs plan
- Fixes docs/SCHEMA_MAP.md § user_favorites (was stale
  (user_id, card_id); actual polymorphic (item_type, item_id) per
  B4's migration)
- Adds docs/MIGRATION_VERIFICATION_RUNBOOK.md — manual
  fresh-Neon-branch vs prod pg_dump diff runbook per architect D5
- Flips .convoys/ship-readiness.md entries:
  - reconcile-historical-add-scripts → RESOLVED
  - retire-graveyard-scripts-after-audit → UNBLOCKED
- Adds two new queued follow-ups surfaced by the architect:
  - unify-user-avatar-column (P3 — dual avatar column smell)
  - drop-dead-cards-columns (P3 — cards.quantity + cards.favorited)

No source-code changes. Docs only.

Co-authored-by: Cursor <cursoragent@cursor.com>
Flatten authenticated sidebar IA, move admin to the profile menu, right-align TopSearchBar actions, and replace placeholder dashboard panels with data from /api/user/stats and /api/user-cards.

Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
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>
Allow consecutive scans without refresh by resetting trackers and counting vision rate limits once per card. Add a fixed card guide, widen detection bounds, and correct object-cover overlay math.

Co-authored-by: Cursor <cursoragent@cursor.com>
Default mobile to manual scan mode with guide tap and center shutter for explicit feedback. Split auto-detect pause from hard verification pause and fall back to guide bounds when shape detection misses.

Co-authored-by: Cursor <cursoragent@cursor.com>
- Move disambiguation-cancel tracker reset into useCameraScanner's new
  resetTrackedCard(cardId) (immutable map + setTrackedCards), wired via
  onTrackerReset — the identification hook no longer mutates state-derived
  objects, clearing the blocking react-hooks/immutability lint error.
- Stub ResizeObserver in test/setup.js so ScannerCamera's workstation
  tests render under jsdom.
- Drop unused eslint-disable directive in CollectionsPageView.

npm run lint: 0 problems; vitest 231/231.
- scanner-desktop-layout: open -> shipped; adds As-shipped section
  (PR #165, squash 938c161, 2026-08-15) with post-merge follow-up refs.
- reconcile-historical-add-scripts: open -> shipped to match its
  existing As-shipped section and ship-readiness RESOLVED entry.
- README: Dokploy/CT102 Postgres stack, lib/sql.js, six rate-limit
  classes over REDIS_URL, Playwright smoke+visual, MinIO/S3 vars,
  inline .env.local contract (no .env.example exists), deckhearth.git
  clone URL, project-structure refresh.
- AGENTS.md: infra banner for the Vercel/Neon -> homelab move, repo
  rename to stwl-labs/deckhearth, Data/Auth/Hosting overview bullets,
  DB-access convention re-pointed at lib/sql.js, Gotcha #12 rewritten
  for REDIS_URL + scan limiter (+ row in limiter table), test counts
  refreshed (231/231 across 43 files), §5 env contract, §7 marked
  legacy-pending-decommission with DOKPLOY_DEPLOY runbook pointer.
- custom_cards migration + CRUD API with catalog twin sync so designs
  appear in My Cards, lists, and decks via normal card joins
- artwork upload to MinIO under card-art/
- /designer page: form-driven live preview, 4 starter frames, PNG export
- /my-designs gallery with edit/delete
- Designer nav entry in sidebar + mobile drawer
- rarity now renders as shape+color symbol anchored inside the type bar
  (circle/diamond/pentagon/star per rarity) — fixes straddling gem alignment
- new flavor_quote field: centered italic quotation with ornamental
  diamond dividers between description/actions/quote
- framed | fullart toggle: full-art bleeds artwork edge-to-edge with
  title/cost top scrim and type/text bottom scrim
- shared pickDesignFields lib so create/update routes cannot drift
- migration 1787685911000: art_mode + flavor_quote columns
- game system selector: standalone, existing system (MTG/Pokemon/Lorcana/
  SWU/FaB/One Piece/Sorcery/Grand Archive — codes match catalog imports),
  or a user's custom game
- custom_games table + CRUD API (private per user, unique names)
- /games hub with create form; /games/[id] space with rename, delete,
  card gallery, and ?game= deep-link into the designer
- catalog twin resolves game: system code, custom game name, or 'Custom'
- my-designs shows each design's game association
- migration 1787693311000
- custom_frames: per-user frames with full 9-slot palette (JSONB),
  unique names; designer frame picker lists them alongside starters,
  click to use, edit/delete via inline editor with live preview
- custom_symbols: upload cost icons (PNG/WebP/SVG, 2MB) keyed by short
  code; re-uploading a code replaces the old icon; ManaPips renders
  icon pips for {CODE} tokens with graceful text fallback
- custom_cards.custom_frame_id links designs to custom frames; API GETs
  join and nest the palette; deleting a frame falls back to starter
- migration 1787700511000
- {CODE} tokens in description/actions/flavor render as inline symbol
  icons (RichText), including inside cost pips
- custom frames gain an optional background texture (upload/replace/
  remove via /api/custom-frames/[id]/texture); renders behind panels
- custom games can be shared to the community (is_public): toggle in the
  game space, public listing at /community/games, read-only game view,
  /api/public/games endpoints (no auth, public rows only)
- /designer/print: multi-card print sheets on US Letter at 300dpi
  (63x88mm cards, 3x3 or 2x2, dashed cut guides, full-sheet PNG export)
- migration 1787711511000
Adds DEFAULT_LAYOUT + IMAGE_FRAME_ROWS constants (fractional art/text window
anchors), a ZoneEditor component for bounding-box layout editing in the
designer, and expands the custom-frames/games CRUD API surface to support
frame image storage and retrieval. Custom card designer pages wire these
together with the existing PNG export pipeline.

See .convoys/card-designer-image-frames.md for scope tracking.
- Export DEFAULT_LAYOUT from lib/frame-palette.js so the custom-frames
  route can import it at runtime (not just the hardcoded copy in tests).
- Fix vitest mock isolation in test/api/custom-frames.test.js: beforeEach now
  uses mockReturnValue instead of mockResolvedValue to avoid resolving the
  default mock in each test; test cases provide specific mock chains with
  mockResolvedValueOnce. Fixes 4 tests that were bleeding state between
  cases due to leftover queued mock values.
- Fix validateLayout test coordinates: art w+h=0.924 and 0.398 are both
  within the 0-1 fraction range so x+w=0.962<1 and y+h=0.982<1 pass.
- Add dedicated validateLayout unit tests (accepts, rejects missing zone,
  rejects out-of-bounds).
- Fix update test mock chain: PUT calls SELECT (found) then SELECT (clash)
  then UPDATE (RETURNING) — provide all three in order.
- Fix DELETE test: owns via SELECT then executes DELETE (2 calls).
feat(scanner): add debug instrumentation for vision pipeline timing
Some checks are pending
CI / Lint (pull_request) Waiting to run
CI / Schema map up to date (pull_request) Waiting to run
CI / Forbidden patterns (9 checks) (pull_request) Waiting to run
CI / Migrations apply (node-pg-migrate) (pull_request) Waiting to run
CI / Unit tests (vitest) (pull_request) Waiting to run
Convoy metrics gate / Require role-event telemetry on convoy PRs (pull_request) Waiting to run
PR Health rollup / Aggregate gate status (pull_request) Waiting to run
Preview smoke / Should run? (pull_request) Waiting to run
Preview smoke / Playwright smoke (pull_request) Blocked by required conditions
Visual diff / Should run? (pull_request) Waiting to run
Visual diff / Screenshot diff (pull_request) Blocked by required conditions
106bd9d592
- Add isDebugMode() + debugLog() helpers to scanner-card-identify.js and use-scanner-identification.js
- Instrument Layer 0 (pgvector), Layer 1 (Tesseract OCR + pg_trgm), Layer 2 (Vision API) with per-layer timing
- Log shutter press, verification outcomes, rate-limit cooldowns, and pipeline totals
- Activate via localStorage.setItem('SCANNER_DEBUG', 'true') or window.__SCANNER_DEBUG = true
- Zero runtime overhead when debug mode is off (isDebugMode() check inlined)
- Add docs/SCANNER_DEBUG_MODE.md with full usage guide and log pattern examples
rstillwell merged commit e5cb8569e5 into main 2026-09-01 18:11:03 -04:00
rstillwell deleted branch feat/scanner-debug-mode 2026-09-01 18:11:03 -04:00
Sign in to join this conversation.
No description provided.