Two root causes for "camera access denied" without a permission prompt:
1. iOS Safari blocks getUserMedia on non-localhost HTTP origins entirely —
no prompt, silent denial. Now detects window.isSecureContext and shows
"HTTPS required" instead of the cryptic error.
2. html5-qrcode does async DOM work (creating video elements, styling)
between the user tap and the actual getUserMedia call, which breaks
iOS Safari's transient user activation window. Now calls
navigator.mediaDevices.getUserMedia() directly in the tap handler to
trigger the permission prompt while the gesture is still active, then
stops that stream and lets html5-qrcode reuse the granted permission.
Also improved error messages for denied permissions with guidance on
how to fix it in browser settings.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
iOS Safari blocks getUserMedia when called outside a user gesture
context. The useEffect auto-start broke the gesture chain, causing
permission denial without ever showing the prompt. Now on iOS the
scanner shows a "Tap to start camera" button that triggers the
permission request within the tap handler. Non-iOS browsers still
auto-start. Also added a "Try again" button on the error state.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replace the cramped horizontal sidebar with a 5-tab bottom nav (Home,
Inventory, Scan, Custody, More) with an elevated scan button. Convert
the 10-column inventory table to card-based list on mobile with
scrollable filter pills and long-press selection. Add full-screen
camera barcode scanner using html5-qrcode with post-scan action sheet.
Set up PWA with vite-plugin-pwa for add-to-home-screen. Convert modals
to bottom sheets, add pull-to-refresh, safe-area padding, and iOS
input zoom fix.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>