without zebrad running, the page is TX-only: tabs see their own
outbound frames but nothing crosses. for UX validation while the
introspector daemon doesn't exist yet, gate a BroadcastChannel
fallback on the ?loopback=1 query param.
behavior:
* with ?loopback=1: after each txFrame completes its modulation,
the raw frame bytes are postMessage'd on a same-origin
BroadcastChannel('zebra-report-loopback'). other tabs of the
same browser subscribe & feed received bytes into onRxFrame
exactly as zebrad would. BroadcastChannel suppresses echo to
sender by spec, so the originating tab does not see itself.
* without ?loopback=1: behavior unchanged. real PA transport
stays the production path.
a yellow banner at the top of the page warns when loopback is
active: "frames cross via BroadcastChannel between same-browser
tabs, NOT via PulseAudio". no risk of mistaking dev mode for
production.