iOS Safari (and the in-app webviews in Messages / WhatsApp / LinkedIn)
don't ship the BarcodeDetector API, so the previous scanner fell
through to the manual paste UI on every iPhone — defeating the point
of "tap to scan."
Vendored cozmo/jsQR (Apache 2.0, ~250KB) and made the scanner pick the
strongest available decoder at setup time:
1. native BarcodeDetector -> Android Chrome, iOS Safari 17+, desktop
2. jsQR canvas loop -> every other browser with getUserMedia
3. manual URL paste -> last-resort if camera unavailable
The jsQR loop draws each video frame into an offscreen canvas, downsamples
to 480px on the long side, and runs jsQR synchronously throttled to
~8 fps to stay under 10% CPU on mid-range Android phones.
Template now shows the <video> element whenever ANY decoder is
available (state.canScan), not just for native. Paste fallback still
visible as a secondary path so a tablet with broken camera permissions
still has a way in.
shopfloor: 19.0.16.0.0 -> 19.0.17.0.0
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>