feat(booth): open the pay/exit modal on a hardware scan regardless of focus

A barcode/QR scanner is an HID "keyboard wedge" — it types the id + Enter into
whatever holds focus. Previously that only worked while the ticket <input> was
focused; a scan with focus elsewhere (or nowhere) went nowhere.

New useScanner hook (apps/web/src/lib/use-scanner.ts): a document-level keydown
listener that detects the scanner's FAST keystroke burst ended by Enter and opens
the pay/exit modal via setActiveTicket — regardless of focus. A gap > 50ms resets
the buffer, so human-paced typing with nothing focused never registers as a scan
(min length 3 guards stray Enters). Keystrokes into an input/textarea/select/
contenteditable are ignored, so the manual ticket field still works by hand. The
hook is paused while a modal is already open — a scan must not abandon an
in-progress payment; the operator finishes/closes, then scans the next car.

Verified at runtime (Playwright): a fast burst with focus on BODY opens the modal;
a second scan while the modal is open is ignored; slow (120ms) human typing does
NOT open it; the manual input submit still opens it. build+lint 14/14.

Claude-Session: https://claude.ai/code/session_01Xcm6ikLgGoCxxHrxtjkk5V
This commit is contained in:
2026-06-21 15:03:01 +02:00
parent 35c10a7310
commit 3ed785c33e
4 changed files with 102 additions and 0 deletions
+10
View File
@@ -1299,3 +1299,13 @@ viewport like /booth: a fixed title+filters, then a two-pane area (shift list |
pane scrolls independently (min-h-0/flex-1 + overflow-y-auto) instead of the whole page growing.
Verified at runtime (Playwright): /shift redirects, rows open the detail modal, layout fills height,
booth still works (0 console errors). build+lint 14/14.
## [2026-06-21] feat | Booth: focus-independent hardware-scan capture
A scan now opens the /booth pay/exit modal no matter what's focused (or if nothing is) — the operator
needn't click the ticket field first. New useScanner hook (apps/web/src/lib/use-scanner.ts): a
document-level keydown listener that detects the HID scanner's fast keystroke burst ended by Enter (gap
> 50ms resets the buffer, so human-paced typing never triggers it; min length 3) and fires
setActiveTicket. Ignores keystrokes into editable fields so the manual ticket input is unaffected;
paused while a modal is open so a scan can't abandon an in-progress payment. Verified at runtime
(Playwright): scan with focus on BODY opens the modal; second scan while open is ignored; slow typing
doesn't trigger; manual form submit still works. build+lint 14/14. See [[booth-console]].