Fix app becoming unresponsive when two browser windows are open: the
root cause was HTTP/1.1 connection pool exhaustion (6-connection limit
per origin). VoiceAlerts was opening 3 SSE streams per window by
default, so two windows produced 8 connections and permanently starved
all regular HTTP requests.
- voice-alerts.js: default all streams to false (opt-in) to stay within
the browser connection limit; existing user preferences in localStorage
are preserved
- routes/alerts.py: replace direct AlertManager.stream_events() with
sse_stream_fanout so both windows receive every alert instead of
competing for the same queue
- routes/bluetooth_v2.py: same fanout fix via subscribe_fanout_queue,
preserving named SSE events (device_update, scan_started, etc.)
Also includes accumulated UI/theming changes: accent-cyan CSS variable
sweep across mode CSS/JS files, standalone dashboard pages, template
updates, satellite TLE data refresh, and tile provider default rename.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replace hardcoded rtl_fm with SDRFactory abstraction layer so WeFax works
with any supported SDR hardware, matching the pattern used by APRS and
other modes. RTL-SDR direct sampling flag preserved for HF reception.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Timer threads now log on fire and catch all exceptions so scheduled
captures never die silently. Frontend connects SSE when the scheduler
is enabled (not only on manual Start) and polls /wefax/status every 10s
as a fallback so the UI stays in sync with auto-fired captures.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replace window.open() with a fullscreen modal matching the SSTV
pattern: toolbar with download/delete SVG buttons, close button,
click-outside-to-close, and confirmation before delete.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Replace blocking stdout.read() with select()-based non-blocking reads
so the decode thread responds to stop within 0.5s
- Make stop() non-blocking by releasing the lock before terminating the
process and removing the redundant wait()
- Move initial scanning SSE event from start() into the decode thread so
it fires after the frontend EventSource connects
- Update frontend stop() to give immediate UI feedback before the fetch
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
rtl_fm subprocess failures (missing tool, no SDR hardware) were silent —
add tool-path check and post-spawn health check in _start_pipeline(),
show errors prominently in the strip status bar (red text + red dot),
and include error detail in scheduler skip events.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Flash the Start button itself with amber pulse when clicked without a
station selected, and show "Select Station" in the strip status text
right next to the button so the error is immediately visible.
Add a 24-hour timeline bar with broadcast window markers, red UTC time
cursor, and countdown boxes (HRS/MIN/SEC) that tick down to the next
broadcast. Broadcasts show as amber blocks on the timeline track with
imminent/active visual states matching the weather satellite pattern.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Fix silent failure when starting without station/frequency selected by
flashing amber on status text and dropdowns. Add auto-capture scheduler
that uses fixed UTC broadcast schedules from station data to
automatically start/stop WeFax decoding at broadcast times.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Implement HF radiofax decoding with custom Python DSP pipeline
(rtl_fm USB → Goertzel/Hilbert demodulation), 33-station database
with broadcast schedules, audio waveform scope, live image preview,
and decoded image gallery. Amber/gold UI theme for HF distinction.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>