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>
All files used hardcoded rgba(74, 163/158, 255, X) values in actual CSS
rules that CSS variable overrides couldn't touch. Solution: add
--accent-cyan-rgb triplet to variables.css root/light/enhanced blocks,
then replace every rgba(74,1xx,255,) occurrence across all CSS files
with rgba(var(--accent-cyan-rgb),). Enhanced tier sets the triplet to
200, 150, 40 (amber), so tscm.css panel bg, index.css card borders,
and all other tinted surfaces go amber automatically.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Each dashboard is a separate HTML page that doesn't inherit the main SPA's
localStorage restore. Add a synchronous tier-restore script before CSS loads
so html[data-ui-tier] selectors fire on first paint.
Also add enhanced/lean tier override blocks to each dashboard CSS to remap
the dashboard-local variables (--bg-dark, --bg-panel, --radar-cyan, etc.)
that variables.css doesn't cover, and add lean-mode scanline/bg hide rules
since components.css is not loaded on these pages.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Brand logo SVGs (.logo, .welcome-logo, .brand-i) now follow --accent-cyan
via CSS rules that override SVG presentation attributes
- proximity-radar.js: sweep, center dot, gradient stops, and selection rings
all use var(--accent-cyan) in style attrs or read getComputedStyle at runtime
- system.js updateGlobePosition: observer point color reads CSS variable
- .bt-detail-address MAC address text uses var(--accent-cyan)
- Enhanced tier gets --visual-edge-cyan/--visual-glow-cyan amber overrides
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Globe.gl WebGL cannot be styled via CSS; read the computed accent color
at init time so Enhanced tier's amber (#c89628) is applied correctly.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
contextlib.suppress(KeyError) around popitem prevents a crash in the SBS
parser thread if stop_adsb() calls clear() concurrently between the len()
check and the popitem call.
Two unit tests verify FIFO eviction semantics and duplicate-key no-op.
Replace four sequential list comprehensions (band → security → hidden → min_rssi)
with a single pass using a helper function. Reduces algorithmic complexity from O(4n)
to O(n) when multiple filters are applied. All WiFi tests pass.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Move fingerprint stability update before early return so it updates even when payload hash matches
- Remove duplicate stability assignment from detect_tracker result block
- Add assertion in test to verify tracker fields are preserved when detection is skipped
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replace manual reimplementation of snapshot/delete logic with actual
store.cleanup() call. Uses mocked time.time to simulate the scenario
where entries refreshed between snapshot and deletion survive due to
re-validation guard.
Fixes: test was passing without actually calling the subject under test
Modify DataStore.cleanup() to minimize lock hold duration:
- Snapshot timestamps under lock (brief O(1) list copy)
- Compute expired keys outside lock (no contention during O(n) scan)
- Re-acquire lock only for deletion with re-validation
(ensures entries refreshed between snapshot and deletion are not deleted)
This reduces blocking of reader threads and prevents latency spikes
during periodic cleanup of large stores (10K+ entries).
Also adds tests:
- test_cleanup_removes_expired_keeps_fresh: basic cleanup behavior
- test_cleanup_does_not_delete_refreshed_entry: re-validation guard
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Enable debug=True on MeshCore.create_ble() to surface verbose logs
- Disconnect any existing BlueZ connection before bleak connects to
avoid conflicts from prior bluetoothctl/pairing sessions
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Store last status message on MeshcoreClient so error details survive
beyond the SSE event (which isn't active during connecting state)
- Status endpoint now returns message field so the frontend can show
the real reason (e.g. 'Connection failed after retries: ...')
- Extend JS polling from 30s to 90s to outlast the backend's 65s
retry sequence (5+15+45s delays) before declaring timeout
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
asyncio.run() called from a gevent-patched Flask thread fails under
gunicorn+gevent. Run the one-shot scan in a ThreadPoolExecutor thread
with its own event loop, matching how AsyncWorker handles it.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Revert route to scan_ble() — scan_ble_sync() lives on AsyncWorker,
not MeshcoreClient; the 500 was caused by our previous fix
- MeshcoreClient.scan_ble() now runs a one-shot asyncio scan when no
worker is active, so Scan works before Connect is pressed
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Scan button shows 'Scanning...' and disables during fetch; shows
'No devices found' or 'Scan failed' on empty/error result; auto-
selects device if only one is returned
- Disconnect button now enabled during 'connecting' state so users
can cancel a stuck connection and retry with a different device
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
After 30s of polling with no response, update UI to 'Connection timed
out' instead of silently leaving the dot stuck on Connecting...
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add base flex properties to #meshcoreVisuals so it fills full panel
height when meshtastic.css hasn't been lazily loaded yet
- Poll /meshcore/status every 2s after Connect click so the UI
transitions out of "Connecting..." when the backend is ready
- Fix Add Contact and Traceroute modals to use .show class pattern
(signal-details-modal uses opacity/visibility transitions, not display)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>