This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
@agicash/qr-scanner is a high-performance web QR code scanner powered by ZXing-C++ WebAssembly. It's a drop-in replacement for nimiq/qr-scanner that feeds full-resolution frames (720-1080px+) to a WASM decoder running in a Web Worker, keeping the UI at 60fps.
Use bun as the package manager. Never use npm, npx, yarn, or pnpm.
bun install # Install dependencies
bun run build # Build library (ESM + CJS + .d.ts via tsup)
bun run test # Run all tests (vitest)
bun run test:watch # Run tests in watch mode
bun vitest run tests/scan-region.test.ts # Run a single test file
bun run typecheck # Type check (tsc --noEmit)
bun run dev # Start demo dev server (Vite + HTTPS)QrScanner (index.ts) ← Public API facade, static methods
└─ Scanner (scanner.ts) ← Orchestrator, composes all components below
├─ CameraManager (camera.ts) ← getUserMedia, stream lifecycle, flash
├─ FrameExtractor (frame-extractor.ts) ← rAF loop, canvas crop, backpressure
├─ Worker (worker.ts) ← Web Worker running zxing-wasm decoder
└─ ScanOverlay (overlay.ts) ← Scan region mask + QR outline SVG
Key data flow: Video stream → FrameExtractor crops to scan region via canvas → ImageData transferred to Worker → zxing-wasm decodes → result posted back → onDecode callback + overlay update.
Backpressure: FrameExtractor skips frames while the Worker is busy. Combined with maxScansPerSecond (default 15), the loop self-tunes without manual FPS management.
Build outputs: tsup produces two separate bundles — the main library (ESM + CJS) and the worker (ESM only, with zxing-wasm bundled in via noExternal).
- No downscaling — unlike nimiq/qr-scanner's 400px cap, scan regions are sent at full resolution to the WASM decoder
- Composition over inheritance — QrScanner wraps Scanner, which composes CameraManager, FrameExtractor, and ScanOverlay
- OffscreenCanvas with fallback — uses OffscreenCanvas when available, falls back to hidden HTMLCanvasElement for older Safari
- Transferable ImageData — frames sent to Worker without copying
- scanImage() uses zxing-wasm directly (no Worker) for static image decoding
Tests use Vitest with jsdom environment. tests/setup.ts provides an ImageData polyfill.
- Unit tests (mocked dependencies): scan-region, camera, frame-extractor, worker
- Integration tests (real WASM decoder): scan-image, scanner
- Test fixtures in
tests/fixtures/— QR PNGs at various densities generated bytests/generate-fixtures.ts
All public types are defined in src/types.ts including ScanResult, ScannerOptions, WorkerRequest/WorkerResponse message types. The worker message types enforce the contract between main thread and Worker.