337 Commits

Author SHA1 Message Date
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 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 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 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 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 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
mitchross ebc838fa9d Add global timezone/12h-24h setting and improve satellite selection
Global time preferences (Settings > Display > Time & Timezone):
- InterceptTime utility in core/utils.js with timezone + 12h/24h support
- Timezone options: UTC, Local, Eastern, Central, Mountain, Pacific
- Time format: 12-hour (AM/PM) or 24-hour toggle
- Defaults to US/Eastern + 12-hour
- Header nav clock updates to use selected timezone and format
- Weather satellite mode delegates to global InterceptTime
- Settings persist via localStorage, change listeners notify all modes

Weather satellite improvements:
- Satellite dropdown defaults to "All Meteor Satellites" showing all passes
- Can still filter to specific satellite (M2-3, M2-4, M2-4-80K)
- Capture button on pass cards auto-selects the correct satellite

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-25 01:24:42 -04:00
mitchross 1e5bc0054d Enhance weather satellite UX with pass geometry, guides, and wider predictions
Pass prediction improvements:
- Widen prediction window to 48h at 5° min elevation (was 24h/15°)
- Add AOS/TCA/LOS pass geometry detail panel with times and bearings
- Fix duration display (was showing seconds labeled as minutes)
- Enhanced pass cards with AOS/LOS times, bearings, and directions
- Add REFRESH button in passes panel header
- Better empty state with clear "set your location" prompt and icon

Countdown and visual:
- Pulse animation on countdown when pass is imminent or active
- Countdown numbers scale up and change color for urgency

Sidebar getting started guide:
- New "Getting Started" section explaining what Meteor satellites are,
  polar orbits, 4-8 passes/day, step-by-step workflow
- "When to look" tips (elevation, day vs night, pass direction)
- "What you need" equipment table with costs
- Collapsed antenna guide by default to reduce initial overwhelm
- Improved offline decode section with clear instructions on where
  to get IQ recordings

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-25 01:13:37 -04:00
mitchross 43fb735e4e Fix Meteor LRPT decoding in Docker and enhance weather satellite UI
Docker fixes:
- Add missing COPY for /usr/local/share/ (pipeline definitions were never
  reaching the runtime image — root cause of silent SatDump failures)
- Add libfftw3-double3 and libfftw3-single3 runtime dependencies
- Handle arm64 vs x86 install path differences (/usr vs /usr/local)
- Split SatDump compile and staging into separate layers for better caching
- Add build-time assertions to catch missing pipelines early

UI enhancements:
- Timezone selector (UTC, Local, Eastern, Central, Mountain, Pacific)
  with localStorage persistence — all time displays update instantly
- Pass analysis bar showing 24h quality breakdown and best upcoming pass
- Enhanced pass cards with cardinal direction (NW→SE), BEST badge
- Console timestamps, log level filters (ALL/SIGNAL/PROG/ERR), COPY/CLR
- Pass count in stats strip
- Demo data mode for UI testing without SDR or live satellite pass
- Meteor M2-4 80k baud fallback pipeline option

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-25 00:05:31 -04:00
James Smith a2bd0e27f9 Fix weather sat handoff: remove defunct METEOR-M2, fire change event
- Remove 'METEOR-M2' (NORAD 40069) from WEATHER_SAT_KEYS — it has no
  entry in WEATHER_SATELLITES and no dropdown option, so the Capture
  button was silently scheduling against the wrong satellite
- Dispatch a 'change' event after setting satSelect.value in preSelect()
  and startPass() so any UI listeners (frequency display, mode info)
  update correctly when the satellite is set programmatically

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-19 23:52:14 +00:00
James Smith 18b442eb21 Fix dashboard startup regressions and mode utilities 2026-03-19 10:37:21 +00:00
James Smith badf587be6 Reset persisted Meteor state on satellite switch 2026-03-18 22:24:12 +00:00
James Smith a995fceb8c Clarify persisted Meteor decode failures 2026-03-18 22:23:09 +00:00
James Smith 2a9c98a83d Show persisted Meteor decode state in weather mode 2026-03-18 22:22:07 +00:00
James Smith 4cf394f92e Persist Meteor decode job state 2026-03-18 22:20:24 +00:00
James Smith 86625cf3ec Fix mode switch re-entry regressions 2026-03-18 21:26:01 +00:00
James Smith 4607c358ed Add ground station automation with 6-phase implementation
Phase 1 - Automated observation engine:
- utils/ground_station/scheduler.py: GroundStationScheduler fires at AOS/LOS,
  claims SDR, manages IQBus lifecycle, emits SSE events
- utils/ground_station/observation_profile.py: ObservationProfile dataclass + DB CRUD
- routes/ground_station.py: REST API for profiles, scheduler, observations, recordings,
  rotator; SSE stream; /ws/satellite_waterfall WebSocket
- DB tables: observation_profiles, ground_station_observations, ground_station_events,
  sigmf_recordings (added to utils/database.py init_db)
- app.py: ground_station_queue, WebSocket init, scheduler startup in _deferred_init
- routes/__init__.py: register ground_station_bp

Phase 2 - Doppler correction:
- utils/doppler.py: generalized DopplerTracker extracted from sstv_decoder.py;
  accepts satellite name or raw TLE tuple; thread-safe; update_tle() method
- utils/sstv/sstv_decoder.py: replace inline DopplerTracker with import from utils.doppler
- Scheduler runs 5s retune loop; calls rotator.point_to() if enabled

Phase 3 - IQ recording (SigMF):
- utils/sigmf.py: SigMFWriter writes .sigmf-data + .sigmf-meta; disk-free guard (500MB)
- utils/ground_station/consumers/sigmf_writer.py: SigMFConsumer wraps SigMFWriter

Phase 4 - Multi-decoder IQ broadcast pipeline:
- utils/ground_station/iq_bus.py: IQBus single-producer fan-out; IQConsumer Protocol
- utils/ground_station/consumers/waterfall.py: CU8→FFT→binary frames
- utils/ground_station/consumers/fm_demod.py: CU8→FM demod (numpy)→decoder subprocess
- utils/ground_station/consumers/gr_satellites.py: CU8→cf32→gr_satellites (optional)

Phase 5 - Live spectrum waterfall:
- static/js/modes/ground_station_waterfall.js: /ws/satellite_waterfall canvas renderer
- Waterfall panel in satellite dashboard sidebar, auto-shown on iq_bus_started SSE event

Phase 6 - Antenna rotator control (optional):
- utils/rotator.py: RotatorController TCP client for rotctld (Hamlib line protocol)
- Rotator panel in satellite dashboard; silently disabled if rotctld unreachable

Also fixes pre-existing test_weather_sat_predict.py breakage:
- utils/weather_sat_predict.py: rewritten with self-contained skyfield implementation
  using find_discrete (matching what committed tests expected); adds _format_utc_iso
- tests/test_weather_sat_predict.py: add _MOCK_WEATHER_SATS and @patch decorators
  for tests that assumed NOAA-18 active (decommissioned Jun 2025, now active=False)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-18 17:36:55 +00:00
James Smith dc84e933c1 Fix setup.sh hanging on Python 3.14/macOS and add satellite enhancements
- Add --no-cache-dir and --timeout 120 to all pip calls to prevent hanging
  on corrupt/stale pip HTTP cache (cachecontrol .pyc issue)
- Replace silent python -c import verification with pip show to avoid
  import-time side effects hanging the installer
- Switch optional packages to --only-binary :all: to skip source compilation
  on Python versions without pre-built wheels (prevents gevent/numpy hangs)
- Warn early when Python 3.13+ is detected that some packages may be skipped
- Add ground track caching with 30-minute TTL to satellite route
- Add live satellite position tracker background thread via SSE fanout
- Add satellite_predict, satellite_telemetry, and satnogs utilities

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-18 11:09:00 +00:00
Smittix 90281b1535 fix(modes): deep-linked mode scripts fail when body not yet parsed
ensureModeScript() used document.body.appendChild() to load lazy mode
scripts, but the preload for ?mode= query params runs in <head> before
<body> exists, causing all deep-linked modes to silently fail.

Also fix cross-mode handoffs (BT→BT Locate, WiFi→WiFi Locate,
Spy Stations→Waterfall) that assumed target module was already loaded.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-12 20:49:08 +00:00
Smittix e687862043 feat: UI/UX overhaul — CSS cleanup, accessibility, error handling, inline style extraction
Phase 0 — CSS-only fixes:
- Fix --font-mono to use real monospace stack (JetBrains Mono, Fira Code, etc.)
- Replace hardcoded hex colors with CSS variables across 16+ files
- Merge global-nav.css (507 lines) into layout.css, delete original
- Reduce !important in responsive.css from 71 to 8 via .app-shell specificity
- Standardize breakpoints to 480/768/1024/1280px

Phase 1 — Loading states & SSE connection feedback:
- Add centralized SSEManager (sse-manager.js) with exponential backoff
- Add SSE status indicator dot in nav bar
- Add withLoadingButton() + .btn-loading CSS spinner
- Add mode section crossfade transitions

Phase 2 — Accessibility:
- Add aria-labels to icon-only buttons across mode partials
- Add for/id associations to 42 form labels in 5 mode partials
- Add aria-live on toast stack, enableListKeyNav() utility

Phase 3 — Destructive action guards & list overflow:
- Add confirmAction() styled modal, replace all 25 native confirm() calls
- Add toast cap at 5 simultaneous toasts
- Add list overflow indicator CSS

Phase 4 — Inline style extraction:
- Refactor switchMode() in app.js and index.html to use classList.toggle()
- Add CSS toggle rules for all switchMode-controlled elements
- Remove inline style="display:none" from 7+ HTML elements
- Add utility classes (.hidden, .d-flex, .d-grid, etc.)

Phase 5 — Mobile UX polish:
- pre/code overflow handling already in place
- Touch target sizing via --touch-min variable

Phase 6 — Error handling consistency:
- Add reportActionableError() to user-facing catch blocks in 5 mode JS files
- 28 error toast additions alongside existing console.error calls

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-12 13:04:36 +00:00