Commit Graph

609 Commits

Author SHA1 Message Date
James Smith 50460396e8 feat: add PagerDirectory JS component 2026-05-21 12:51:41 +01:00
James Smith 6f33a8e4f4 fix: add display flex to pdir-panel, use accent-purple-rgb variable
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-21 12:49:54 +01:00
James Smith 3def62d530 feat: add pager directory view CSS 2026-05-21 12:46:31 +01:00
James Smith db0b7ad059 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 7e138622fa 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 8e89979770 fix: introduce --accent-cyan-rgb to make all opacity variants theme-aware
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>
2026-05-20 08:53:25 +01:00
James Smith 4ac996a538 fix: apply UI tier to standalone dashboard pages (ADS-B, AIS, Satellite)
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>
2026-05-20 08:44:29 +01:00
James Smith 383ec27d4e revert: restore brand logo to fixed blue (#00d4ff) — brand identity not UI chrome
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-20 08:41:11 +01:00
James Smith d28cb60916 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 7adefd0cfd 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 cb61cd7c7d 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 485312df6e 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 ea5fb0911f 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 29133dd330 fix: use html[data-ui-tier] selector to beat index.css :root specificity; add body tier rules to index.css 2026-05-19 22:49:40 +01:00
James Smith 73f8f7f8f7 fix: hide decorative elements (scanline, globe, radar-bg) in lean tier 2026-05-19 22:42:34 +01:00
James Smith 2c96e0532e chore: remove orphaned icon-effects CSS (replaced by tier toggle) 2026-05-19 22:30:25 +01:00
James Smith 5aba99f998 feat: add Display Mode step to first-run setup modal 2026-05-19 22:23:58 +01:00
James Smith cc9ca6375d 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 369da480d7 feat: add lean/enhanced component overrides; retire data-animations component CSS 2026-05-19 22:18:48 +01:00
James Smith b6f37ea64b feat: add lean/enhanced layout overrides; retire data-animations CSS
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-19 22:16:50 +01:00
James Smith 5d0162628a feat: add lean/enhanced body background overrides 2026-05-19 22:14:25 +01:00
James Smith b57ea83f82 feat: add lean and enhanced CSS variable override blocks 2026-05-19 21:55:01 +01:00
James Smith cfa5333845 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 1f573ee6ed 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 5af970efc0 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 40c000d60d 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 6232e650a7 fix(meshcore): make strip and body direct children of visuals container
Remove the intermediate #meshcoreMode wrapper div that was breaking the
flex height chain. Strip and body are now direct children of
#meshcoreVisuals (matching the Meshtastic pattern), so flex: 1 propagates
correctly and the content fills the full panel height.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-13 12:16:02 +01:00
James Smith a63939b0f0 fix(meshcore): add mesh-sidebar-hidden rules to meshcore.css
The sidebar-hiding CSS lives only in meshtastic.css, which is lazily
loaded and may not be present when switching directly to Meshcore mode.
Duplicating the three rules into meshcore.css ensures the generic
sidebar is correctly hidden and the output panel fills the screen
regardless of load order.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-13 11:32:14 +01:00
James Smith 4f717bb454 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 b6786822e8 fix(meshcore): move UI into visuals container, fix layout
meshcoreMode partial was inside the generic .sidebar which gets hidden
when meshcore mode is active. Moved the include into meshcoreVisuals
(inside the output panel) — matching the same pattern as Meshtastic.
Also overrides mesh-visuals-container's column/padding defaults so the
meshcore sidebar+main row layout renders correctly.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-13 10:15:14 +01:00
James Smith a392b30cd9 fix(meshcore): show mode panel when active
meshcore.css was missing the .active display rule, so the meshcoreMode
div (display:none inline) was never made visible when the mode was
selected, leaving only the generic sidebar visible.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-13 10:10:25 +01:00
James Smith bd6a5f132e 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 8caeb4b961 fix(drone): fix main panel height collapse in flex output container
Replace height:100% with flex:1+min-height:0 on .drone-visuals-container
so it fills the flex-column .output-panel correctly (height:100% collapses
inside a scroll container). Add min-height:400px to .drone-main-map so
Leaflet has pixel dimensions to render into.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-13 09:45:00 +01:00
James Smith 2537c6d140 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 9fd60b6839 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 ba3ee1575e 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 09142420ab 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 fca950bf98 feat(meshcore): add frontend JS module (IIFE, SSE, map, telemetry, traceroute) 2026-05-11 14:58:45 +01:00
James Smith e3a1f8430c feat(meshcore): add CSS for Meshcore mode
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-11 12:56:58 +01:00
James Smith 037c9c5aee 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 347659c275 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 f300f164c9 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 741e9a18f8 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
James Smith efb2a2a8f0 feat(drone): add HTML partial, CSS, and index.html mode panel wiring
- Create templates/partials/modes/drone.html with drone mode sidebar panel
- Create static/css/modes/drone.css with scoped drone UI styles
- Wire drone mode into index.html: CSS map entry, partial include, classList toggle

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-03 17:41:02 +01:00
Mitch Ross 11b6cd6375 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 bd3e3369f2 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 510c85e73f perf(css): fix rendering slowdown on low-power hardware (Pi5)
- Remove backdrop-filter: blur(5px) from .card and .panel — on ARM/Linux
  Chromium this is software-rendered, causing severe CPU overhead at 42+
  instances. The opaque surface gradient makes blur imperceptible anyway.
- Remove inset vignette box-shadow from .panel added in f62c4b4
- Rewrite panel-pulse keyframes to animate opacity only (was box-shadow,
  which triggers CPU repaint every frame; opacity is compositor-only)
- Gate body::before and .visuals-container::after scanline pseudo-elements
  under [data-animations="off"] — the toggle was blind to both
- Gate panel-indicator pulse under [data-animations="off"] for consistency

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-19 14:54:34 +01:00
James Smith 7e19085080 chore: clean up project root
- Delete stale aircraft_db.json + aircraft_db_meta.json (code uses data/adsb/)
- Delete orphaned gp.php (TLE data fetched at runtime from celestrak.org)
- Move oui_database.json → data/ and update path in data/oui.py
- Move favicon.svg → static/ and update send_file path in app.py

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-15 13:05:35 +01:00
James Smith 447593669c fix(maps): add !important to glass popup overrides to beat global index.css rules 2026-04-13 22:29:23 +01:00
James Smith 95c3f434b9 feat(maps): add map-utils.css for HUD panels, glass popup, range ring labels 2026-04-13 22:27:19 +01:00