414 Commits

Author SHA1 Message Date
James Smith fe222c0393 fix: don't restore #output visibility in non-sensor modes
SensorDashboard.applyViewState was resetting output.style.display=''
in the else branch, undoing switchMode's modesWithVisuals hide for
waterfall, morse, ook, and every other mode with its own visuals.
Only touch #output when mode === 'sensor'.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-21 21:12:09 +01:00
James Smith e7f13a5856 fix: escape channel, snr, and reading values in sensor dashboard cards 2026-05-21 13:01:46 +01:00
James Smith a9ed367148 feat: add SensorDashboard JS component 2026-05-21 13:00:09 +01:00
James Smith 484d9ce21b fix: refresh directory timestamps on applyViewState 2026-05-21 12:55:49 +01:00
James Smith 9353527e1b feat: add PagerDirectory JS component 2026-05-21 12:51:41 +01:00
James Smith 592d11aae2 feat: add graticule toggle control to all Leaflet maps
Adds a bottomleft grid button (MapUtils.addGraticuleControl) to every
map in the app — Meshtastic, MeshCore, Drone, SSTV/ISS, BT Locate,
WebSDR, and Weather Satellite — defaulting to visible. The weather
satellite map's bespoke addStyledGridOverlay() is removed in favour of
the shared implementation. Also updates map-utils.css with button
styles and map-utils.js with the new addGraticuleControl() method.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-21 11:09:39 +01:00
James Smith a3f2fa7b88 fix: resolve two-window hang and sweep UI/theming updates
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>
2026-05-20 22:01:10 +01:00
James Smith 9d72c88a28 fix: sweep final hardcoded cyan from mode JS files and CSS
- proximity-radar.js: fix missed dot stroke in new-device creation path
- gps.js: GPS constellation color via object getter; globe atmosphere reads CSS var
- websdr.js: globe atmosphere, map markers, popup buttons, point label use CSS var
- subghz.js: canvas strokeStyle reads --accent-cyan
- sstv.js: ISS track polyline reads --accent-cyan
- app.js: info message border-left uses var(--accent-cyan)
- subghz.css, gps.css: replace all #00d4ff with var(--accent-cyan)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-19 23:08:49 +01:00
James Smith e1922d7a30 fix: signal-guess why-button hover uses --accent-cyan CSS variable
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-19 23:05:57 +01:00
James Smith c48d66d1b4 fix: replace remaining hardcoded cyan in map utilities and mode JS files
- map-utils.js: range rings and reticle crosshair SVG use --accent-cyan
- drone.js: trail polyline color reads --accent-cyan for non-threat contacts
- weather-satellite.js: NOAA APT pass track reads --accent-cyan
- space-weather.js: solar wind chart border/bg/axis ticks read --accent-cyan

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-19 23:05:38 +01:00
James Smith fbea33e7cb fix: replace hardcoded cyan with CSS variable across brand SVGs and components
- 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>
2026-05-19 23:04:47 +01:00
James Smith af26a01703 fix: read --accent-cyan CSS var for globe atmosphere and point color
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>
2026-05-19 23:00:50 +01:00
James Smith 2b25d5cbad feat: add Display Mode step to first-run setup modal 2026-05-19 22:23:58 +01:00
James Smith 0a75322ad1 feat: replace animations toggle with display mode selector in settings
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-19 22:22:05 +01:00
James Smith 98e01f4c5b fix(meshcore): surface backend error messages and extend polling window
- 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>
2026-05-13 20:59:33 +01:00
James Smith ae5664dbb4 fix(meshcore): BLE scan feedback and cancel-during-connecting
- 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>
2026-05-13 20:42:25 +01:00
James Smith c5fdf7f7e9 fix(meshcore): show error state when connection times out
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>
2026-05-13 20:39:16 +01:00
James Smith 2cdf156cd0 fix(meshcore): fix layout height, connection polling, and modal visibility
- 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>
2026-05-13 16:59:59 +01:00
James Smith 7940728b30 fix(meshcore): restyle to strip layout, fix map tiles
Replaced inner-sidebar layout (which collided with the generic app
sidebar) with a Meshtastic-style top connection strip + body row.
Contacts/nodes panel sits left of the tabbed content area, matching
the established pattern. Map now uses Settings.createTileLayer() with
a dark CartoDB fallback instead of plain OSM light tiles.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-13 10:23:31 +01:00
James Smith e397a69dae fix(drone): use Settings tile layer for theme-aware map
Replace hardcoded OSM tiles with Settings.createTileLayer() + registerMap()
so the drone map respects the user's map theme preference and switches
automatically with light/dark theme changes. Falls back to CartoDB dark_all
if Settings is unavailable.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-13 09:58:04 +01:00
James Smith 410225d54d fix(drone): conform to established SPA patterns throughout
- Device population: move refreshDroneDevices() inline to index.html
  (same pattern as refreshTscmDevices) and call it from switchMode
  alongside DroneMode.init(); remove _refreshDevices/populateSelect
  from drone.js which was never guaranteed to run before lazy-load
  completed, causing selects to stay on "Loading…" permanently

- IIFE pattern: change from named IIFE + window.DroneMode assignment
  to var DroneMode = (function(){...return{...}})() matching OOK/
  SpyStations convention

- Init guard: add _initialized flag (OOK state.initialized pattern);
  re-entry after destroy() re-registers map/SSE cleanly without
  duplicating click listeners on every mode switch

- Lifecycle: destroy() resets _initialized = false so map and SSE
  are correctly rebuilt on re-entry

- Stop phase: add isDroneRunning tracking variable in index.html;
  _setRunningUI() syncs it; switchMode stop phase now POSTs
  /drone/stop when leaving drone mode while active, matching TSCM

- /drone/devices: add monitor_capable field to WiFi interfaces,
  add running_as_root and warnings array to response (mirrors
  /tscm/devices shape); add os import; show privilege warning div
  in drone.html when not running as root

- drone.html: remove for= attribute from SDR label (plain <label>
  inside .form-group matches TSCM convention); add droneDeviceWarnings
  div for privilege warnings

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-13 09:33:38 +01:00
James Smith 4ba8a40af9 feat(drone): replace freeform inputs with populated device selects
Add /drone/devices endpoint that enumerates available WiFi interfaces
(via iw/iwconfig) and RTL-SDR devices (via SDRFactory.detect_devices),
matching the pattern used by TSCM.

Sidebar WiFi interface and RTL-SDR inputs are now <select> elements
populated on init() from /drone/devices, consistent with how other
modes expose hardware selection. HackRF checkbox remains as a toggle
since it's a binary capability rather than an enumerated device list.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-13 09:25:21 +01:00
James Smith 6523686aca feat(drone): add main visuals panel with map and contact list
- Sidebar inputs now use form-group/label pattern matching other modes
- Move map and contact list out of sidebar into a dedicated droneVisuals
  main panel (same pattern as tscm, spystations, etc.)
- droneVisuals: stats header (contacts / non-compliant / high-risk),
  left contact card panel, and full-height Leaflet map on the right
- Wire droneVisuals into switchMode display toggle and modesWithVisuals
  so the shared signal-feed output is hidden when drone mode is active
- Add invalidateMap() to force Leaflet to recalculate after the
  container becomes visible
- Stats now update both sidebar counts and main panel values

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-13 09:16:54 +01:00
James Smith 2475e5dd5a fix(drone): show sidebar panel and expose SDR config options
Remove inline style="display: none;" that was preventing the droneMode
panel from becoming visible when the active class was toggled — inline
styles override CSS class rules without !important. Add RTL-SDR device
index and HackRF toggle inputs that the backend already accepted but
were never surfaced in the UI; wire them through to the /drone/start
POST body.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-13 09:07:42 +01:00
James Smith 36a1542176 feat(meshcore): add frontend JS module (IIFE, SSE, map, telemetry, traceroute) 2026-05-11 14:58:45 +01:00
James Smith 62e53c5dfa fix(adsb): fix aircraft photo display and add Drone Intelligence docs
- Fix stale DOM refs in fetchAircraftPhoto: elements were captured before
  await fetch(), but showAircraftDetails rebuilds innerHTML on every RAF
  update, leaving the async path writing to detached nodes. Now re-queries
  the DOM after await, and the cache (synchronous) path queries inline so
  refs are always fresh.
- Add thumbnail fallback in aircraft_photo route: fall back to thumbnail
  when thumbnail_large.src is absent rather than returning null.
- Add Drone Intelligence to nav, help modal, cheat sheets, README, and docs.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-05 09:24:30 +01:00
James Smith 333c5cf8d3 feat(drone): merge Drone Intelligence module
Multi-vector UAV detection mode: Remote ID (WiFi/BLE ASTM F3411),
RTL-SDR 433/868MHz control-link detection, HackRF 2.4/5.8GHz wideband.

Workers feed a shared observation queue; DroneCorrelator merges into
DroneContact objects with TTL store, risk scoring, and SSE streaming.
Frontend: two-panel sidebar + Leaflet map with contact cards and trails.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-05 08:36:27 +01:00
James Smith 8632e31c01 fix(drone): resolve critical pipeline, frontend, and input validation issues
Data pipeline (critical): scanners/detectors now write to a separate _obs_queue;
a relay thread reads observations and calls correlator.process(), which emits
processed DroneContact dicts to drone_queue for SSE. Without this the SSE stream
received raw unserializable dataclass objects causing JSON errors.

Frontend (critical):
- Add droneContactList container to drone.html so contact cards render
- Add droneMap container and initialize Leaflet in drone.js init()
- Define dsc-distress-pulse keyframes in drone.css (was referenced but missing)
- Fix SSE reconnect: null _sse before setTimeout to prevent _connectSSE no-op loop

Other fixes:
- Validate rtl_sdr_index with validate_device_index(), return 400 on bad input
- Move _ensure_workers() inside _drone_lock to prevent double-initialization race
- Add double-call guard to RemoteIDScanner.start()

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-03 21:47:12 +01:00
James Smith 14e6305aa4 feat(drone): add frontend JS, modeCatalog entry, and switchMode wiring
Creates static/js/modes/drone.js IIFE module (SSE consumer, map markers,
contact cards, start/stop controls) and wires it into index.html via
INTERCEPT_MODE_SCRIPT_MAP, modeCatalog (intel group), and switchMode
init/destroy handlers.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-03 17:44:07 +01:00
Mitch Ross 4a149525bd Merge main into misc-fixes and address PR #202 review
Sync with upstream main and fix required items from review:

- updateTimelineLabels() now uses InterceptTime API (getTimezone/getIANA)
  instead of the stale selectedTimezone/TZ_MAP globals that were removed
  during the earlier InterceptTime refactor — fixes ReferenceError on TZ
  change and pass refresh.

- Remove profiles: [basic] from the intercept service in
  docker-compose.yml so bare `docker compose up -d` still starts the
  main service. Profile-gated services (intercept-history, adsb_db)
  stay as-is.
2026-04-24 16:34:09 -04:00
James Smith 7cf94cce14 fix(sstv): fix inaccurate ISS orbit tracking — three root causes
1. iss_schedule() was importing TLE_SATELLITES directly from data/satellites.py
   (hardcoded, 446 days stale) instead of the live _tle_cache kept fresh by
   the 24h auto-refresh. Add get_cached_tle() to satellite.py and use it.

2. Ground track was a fake sine wave (inclination * sin(phase)) that mapped
   longitude offset directly to orbital phase, ignoring Earth's rotation under
   the satellite (~23° westward shift per orbit). Replace with a /sstv/iss-track
   endpoint that propagates the orbit via skyfield SGP4 over ±90 minutes, and
   update the frontend to call it. Past/future track rendered with separate
   polylines (dim solid vs bright dashed).

3. refresh_tle_data() updated _tle_cache in memory but never persisted back to
   data/satellites.py, so every restart reloaded the stale hardcoded TLE. Add
   _persist_tle_cache() called after each successful refresh.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-20 15:37:02 +01:00
James Smith 51f8a6f65b fix(maps): fix _upgradeTiles race guard, interval leak, graticule events, ring labels
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-13 22:24:23 +01:00
James Smith 99edea33e3 feat(maps): add MapUtils shared map initialisation utility
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-13 22:19:44 +01:00
James Smith f97782724e fix(maps): exclude stadia key from localStorage cache
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-13 22:11:34 +01:00
James Smith e4df3eaecb feat(maps): add Stadia dark + tactical tile providers with API key support
- Add offline.stadia_key to OFFLINE_DEFAULTS in routes/offline.py
- Add stadia_dark and tactical tile providers to Settings.tileProviders
- Update getTileConfig() to inject Stadia API key or fall back to CartoDB dark
- Add setStadiaKey() method for saving and applying the API key
- Show/hide Stadia key row in setTileProvider() and _updateUI()
- Add Stadia options to tile provider select in settings modal
- Add Stadia API key input row to settings modal
- Add TDD tests for stadia_key backend

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-13 22:04:07 +01:00
James Smith 6572119360 fix(bluetooth): fix locate button not switching to bt_locate mode
Remove the split fast-path in doLocateHandoff that called BtLocate.handoff()
directly when the module was already loaded. That path relied on handoff()
internally calling switchMode, causing a double switchMode in the lazy-load
path and no guaranteed mode switch in the fast path.

Now doLocateHandoff always calls switchMode('bt_locate') first (lazy-loading
script/styles as needed), then calls BtLocate.handoff() in .then(). Removed
the redundant switchMode call from BtLocate.handoff() since the caller owns
the mode transition.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-05 13:54:16 +01:00
James Smith 71e5599300 feat(bluetooth): WiFi-style 2-line device rows
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-29 16:07:51 +01:00
James Smith 6967a44620 feat(bluetooth): scan indicator JS, sort controls, renderAllDevices
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-29 15:31:02 +01:00
James Smith ab4745c70a fix(bluetooth): update filter container ID to btFilterGroup, document sticky offset 2026-03-29 15:29:07 +01:00
James Smith d45b8bc2fb feat(bluetooth): CSS animated radar sweep with trailing glow arc
Replaces the requestAnimationFrame loop in proximity-radar.js with a
CSS @keyframes rotation on .bt-radar-sweep, mirroring the WiFi radar
pattern. Adds two trailing arc paths for a glow effect and updates
setPaused() to toggle animationPlayState instead of the rAF flag.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-29 14:35:45 +01:00
James Smith 7a4dbb8260 fix(wifi): remove dead chart pendingRender flag, dead radar highlight call, CSS.escape client mac
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-26 22:58:58 +00:00
James Smith 73b227c49b feat(wifi): network detail panel replaces slide-up drawer
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-26 22:48:23 +00:00
James Smith bfbf06f5c5 fix(wifi): render heatmap and security ring even when filter yields no networks 2026-03-26 22:40:08 +00:00
James Smith e5a0635418 feat(wifi): channel heatmap and security ring chart
Replace static channel bar chart and security dots with a scrolling
2.4 GHz channel heatmap (up to 10 scan snapshots) and an SVG donut
security ring showing WPA2/WPA3/WEP/Open network distribution.
2026-03-26 22:38:31 +00:00
James Smith 56ebdd7670 fix(wifi): radar bssidToAngle divisor, Firefox SVG transform-origin, zone label clarity
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-26 22:31:21 +00:00
James Smith 4c37d39e07 fix(wifi): remove duplicate zone count update from updateStats 2026-03-26 22:28:44 +00:00
James Smith d1d44195c1 feat(wifi): animated SVG proximity radar with sweep rotation
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-26 22:26:47 +00:00
James Smith 0dbcb175c0 fix(wifi): XSS fix for onclick handler, unknown security badge, null rssi handling
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-26 22:23:40 +00:00
James Smith ea348b3360 feat(wifi): replace table with styled div network rows
Replaces the 7-column <table> network list with flex div rows featuring
two-line layout (SSID + security badges on top, signal bar + meta on
bottom), coloured left-border threat indicators, and new sort controls.
Renames selectedNetwork → selectedBssid and updateNetworkTable → renderNetworks throughout wifi.js.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-26 22:18:51 +00:00
James Smith 36399cf4aa feat(wifi): enhanced status bar with open count and scan indicator
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-26 22:11:48 +00:00