macOS keystroke injection from a CLI-launched Python hits multiple
TCC permission walls (Accessibility AND Automation, both attaching
to identities macOS often can't resolve cleanly). After bouncing
through Quartz, AppleScript, and pyautogui fallbacks, none of them
worked reliably in our test environment.
Switch to a proper IPC channel instead of pretending to be a
keyboard.
Daemon (wedge.py):
- Adds a ThreadingHTTPServer on 127.0.0.1:8765 exposing /events
- SSE stream pushes each detected UID as one event
- 30s keep-alive comments to keep idle connections open
- CORS: Access-Control-Allow-Origin: * (kiosk page may be on any
client-domain HTTPS origin; SSE source is always localhost)
- Keystroke injection kept as best-effort fallback for non-SSE
clients
Kiosk JS (fusion_clock_nfc_kiosk.js):
- Adds startWedgeSseListener() that opens EventSource to
http://localhost:8765/events on setup
- On message: same handleTap()/_onEnrollTap() flow as Web NFC + HID
- EventSource auto-reconnects; first error is logged then silenced
- http://localhost is a "potentially trustworthy origin" so this
works from https:// pages without mixed-content blocking
Result: ACR122U + wedge.py daemon now drives the kiosk with zero
macOS permission prompts and no focused-window dependency. Same
input plumbing as Web NFC and HID — penalty/photo/activity log
fire identically.
Bump fusion_clock to 19.0.3.3.0.
The NFC kiosk previously required Web NFC, which is Android-Chrome-only.
This blocked desktop testing and locked us to a single hardware path.
Add a keyboard-wedge listener that captures keystrokes from USB HID NFC
readers (the standard Sycreader/Yanzeo class). The listener buffers hex
chars + separators, flushes on Enter (or 600ms idle as fallback for
readers without a terminator), and routes the UID through the same
handleTap()/_onEnrollTap() codepath as Web NFC. Photo verification,
penalty calc, and activity logging all fire identically.
Make the setup button tolerant: try Web NFC, but treat its absence as
non-fatal. USB HID always activates. Only hard-fail when photoRequired
is True AND the camera is unavailable.
Result: same kiosk page now works on Android Chrome (Web NFC), desktop
Chrome with a USB reader, or both at once.
Bump manifest to 19.0.3.2.0.
Visual rewrite of the NFC kiosk page:
- Animated mesh gradient background (drifts on a 28s loop)
- Glass-panel state cards with backdrop-filter blur
- Animated SVG NFC icon (concentric waves emanate from a chip)
- Company logo pulled from res.company.logo, displayed in header
- Dominant-hue extraction from logo sets --nfc-h CSS var; entire
palette interpolates from that one HSL hue
- Success burst (green glow + scale), error shake, smooth state fades
- Reduced-motion fallback respects prefers-reduced-motion
- Glass numpad + employee picker in Enroll Mode
CRITICAL FIX: scoped all kiosk styles under :has(#nfc_kiosk_root) so
they no longer leak into other frontend pages. Previous version applied
html/body overflow:hidden + display:none on header/footer globally,
breaking website scrolling and chrome on every frontend page.
Add Ctrl+Shift+T keyboard shortcut (guarded by debugEnabled / nfc_kiosk_debug
setting) that prompts for a UID and fires _onEnrollTap or handleTap depending
on currentState (ENROLL vs IDLE). Persists last-used UID in localStorage.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replaces the Task 18 stub renderEnroll with the full four-phase
implementation (password numpad → employee picker → tap-to-enroll →
result), adds _onEnrollTap wired to the NFC reading event, and exposes
it via window.__nfcKiosk.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Replace camera stub with real getUserMedia + canvas capture. Setup button
now starts NFC reader and camera together; camera failure is non-fatal when
photo is not required.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>