655 Commits

Author SHA1 Message Date
James Smith 065b4778a9 feat: TSCM sweep metadata, cleared devices, and examiner ignore list
Four new features requested by TSCM users:

- Site/Location and Examiner name fields appear at the top of the
  sweep config; both are embedded in HTML and PDF/annex reports.
- Mark Cleared button on every live device item dims the entry with a
  CLEARED badge and excludes it from generated reports. Cleared state
  resets at the start of each new sweep. The report executive summary
  shows a count of cleared devices.
- Ignore List stores the examiner's own devices persistently in
  localStorage. Ignored devices are filtered from the live display and
  all report exports. An Ignore button appears on every device item;
  the sidebar Examiner Ignore List section shows current entries with
  per-item removal and a clear-all button.
- Site/examiner params forwarded to PDF and annex server routes so
  the text report header includes them.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-05 14:37:08 +01:00
James Smith 8b2879c71f feat: add category filter to TSCM report exports
Users can now choose which risk categories to include in generated
reports — High Interest, Needs Review, and Informational — via three
checkboxes that appear after a sweep completes. Applies to the HTML
report, PDF, JSON annex, and CSV annex. Defaults to High Interest +
Needs Review (Informational excluded) to keep reports concise.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-05 13:34:09 +01:00
James Smith 1483682172 fix: load SignalIdModal on dashboard pages; clear status on zero matches
- Added signal-id-modal.js script to base_dashboard.html (nav button was silent on dashboard pages)
- Added else branch to clear "Searching…" status when zero matches returned

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-03 09:08:34 +01:00
James Smith c6977eedde feat: wire SignalIdModal to waterfall and global nav
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-03 08:56:26 +01:00
James Smith ea7e3941d4 feat: add SignalIdModal IIFE component with scored results
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-03 08:49:55 +01:00
James Smith 2f2d0dac9b fix: meshcore serial connection and battery calculation issues
- Fix _pollUntilConnected overwriting real error with generic timeout
  message — stop polling once backend reaches error state
- Fix battery % calculation: was battery_mv/42 (71% at empty); now
  (battery_mv-3000)/12 for a proper 0-100% LiPo curve
- Improve auto-detect: scan available ports rather than blindly using
  /dev/ttyUSB0; update label to show fallback so users know what to expect
- Add port refresh button (↻) — previously required switching modes
  and back if device was plugged in after panel opened
- Add baud rate selector to serial config strip (default 115200,
  options up to 921600) and wire it through to the connect request

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-01 14:22:10 +01:00
James Smith ec59ef0e42 test: mode registry consistency checks; fail fast if registry missing
Also documents the registry-driven mode integration in CLAUDE.md.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-12 08:11:04 +01:00
James Smith 71f44c0f40 refactor: registry-driven mode init dispatch in switchMode
A failing mode init now logs instead of aborting the remainder of
switchMode (deliberate hardening; previously an exception skipped
title/visuals updates).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-12 08:03:40 +01:00
James Smith 1348d16705 refactor: derive sidebar toggles and destroy map from registry
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-11 21:46:40 +01:00
James Smith fc494a763e refactor: derive modeCatalog and modesWithVisuals from registry
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-11 21:29:37 +01:00
James Smith 844a1698af feat: introduce frontend mode registry (no behaviour change)
window.INTERCEPT_MODES mirrors the existing modeCatalog, sidebar
toggles, visuals list, destroy map, and switchMode init branches.
Derivations follow in subsequent commits; a temporary console guard
verifies registry/catalog parity at runtime.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-11 21:16:40 +01:00
James Smith 746d195d76 fix: stabilize test suite and repair frontend/backend wiring
- meshcore pin >=2.3.0 (EventType.STATS_CORE floor); setup.sh derives
  optional packages from requirements.txt; Python 3.10 warning
- agent-mode wifi clients proxy route + bare-array response handling
- remove dead AIS/ACARS/VDL2 SPA wiring and orphaned partials/CSS
- agent TLE download to data/tle/ (was littering repo root as gp.php)
- gate deferred background init off under pytest (mock-pollution race)
- complete Popen mocks (context manager protocol, communicate tuples)
- real pipe fds in weather-sat decoder tests (fd 10/11 collision caused
  10s SQLite stalls); satellite tests no longer rewrite data/satellites.py
- register 'live' pytest marker, excluded by default
- update stale test assertions to current APIs

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-11 16:42:33 +01:00
James Smith 0a6ad837d8 fix: prevent full page reload when clicking Main Dashboard button
Intercept .nav-dashboard-btn clicks to perform SPA-style navigation
instead of a full page reload. After switchMode() updates the URL to
/?mode=<x> via pushState, clicking href="/" previously caused a round-
trip reload. Now the click handler stops active scans, destroys the
current mode, shows the welcome overlay, and pushes '/' onto history.

Also update popstate to restore the welcome page when navigating back
to '/' with no mode param.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-21 22:07:41 +01:00
James Smith 1f972a27b5 fix: hide signalViewWrap entirely in modesWithVisuals to prevent layout bleed
The flex container was still occupying space even when all its children
were hidden, causing a blank box to overlap mode-specific content in
Bluetooth, WiFi, SSTV and other modesWithVisuals.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-21 21:21:13 +01:00
James Smith c2197bbdcc fix: move applyViewState calls after output display override in switchMode
switchMode() forces output.style.display='block' for modes not in
modesWithVisuals (line ~4906). Our applyViewState calls were placed
before this line, so the override undid the dashboard hide. Moving
them after ensures SensorDashboard can correctly hide #output in
dashboard mode.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-21 17:47:42 +01:00
James Smith 09ec672247 feat: wire PagerDirectory and SensorDashboard into pager and sensor modes
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-21 14:25:13 +01:00
James Smith 4f9f32bfef feat: add HTML scaffolding for pager directory and sensor dashboard views
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-21 12:39:57 +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 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 8c8e0b0f62 fix: use var(--accent-cyan) in wifi proximity radar SVG so enhanced tier colors it amber 2026-05-19 22:57:24 +01:00
James Smith 47ab080ca8 feat: retire data-animations from index.html, login.html, and base_dashboard.html 2026-05-19 22:25:38 +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 b1e238b398 feat: add tier toggle button to nav; migrate data-animations restore to data-ui-tier 2026-05-19 22:20:31 +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 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 c554cbcacf fix(meshcore): add to nav and fix welcome card styling
Meshcore was missing from both the desktop Wireless dropdown and mobile
nav. The welcome card also used non-standard div/emoji markup instead of
the SVG icon pattern used by every other mode, causing wrong font and
colour rendering.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-13 10:02:45 +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 8dd30ea847 feat(meshcore): wire Meshcore into index.html (14 insertion points) 2026-05-11 15:47:27 +01:00
James Smith 93b93fe3d6 feat(meshcore): add HTML partial (sidebar, tabs, modals)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-11 12:58:29 +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 a0306f50d7 fix(maps): remove duplicate overlays — drop HUD panels from APRS/GPS (have own header), drop MapUtils range rings from ADS-B (drawRangeRings() owns them) 2026-04-13 23:46:03 +01:00
James Smith c469c6c44e fix(maps): remove defer from map-utils.js in index.html for consistency 2026-04-13 23:33:40 +01:00
James Smith 3083db603f refactor(radiosonde): use MapUtils.init + Settings tile layer on sonde map
Replace hardcoded L.map() + CartoCD dark tile layer with MapUtils.init()
and add tactical overlays. Adds test verifying the cartocdn URL is gone.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-13 23:27:36 +01:00
James Smith 5ce248792a fix(aprs): correct aprsMarkers variable name and reset aprsMapOverlays on teardown 2026-04-13 23:15:53 +01:00
James Smith ee256f640e refactor(aprs,gps): use MapUtils.init + HUD panels on APRS and GPS maps
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-13 23:12:46 +01:00
James Smith f6adc0e063 fix(satellite): use MapUtils._buildReticle consistently in updateObserverMarker 2026-04-13 23:03:46 +01:00
James Smith 239369ab9f refactor(satellite): use MapUtils.init + HUD on ground track map
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-13 22:56:20 +01:00
James Smith f6174aa5f4 refactor(ais): use MapUtils.init + HUD panels on vessel map
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-13 22:41:26 +01:00
James Smith 80a8bf501b refactor(adsb): use MapUtils.init + tactical overlays on radar map
Replace custom createFallbackGridLayer/upgradeRadarTilesFromSettings with
MapUtils.init(), add range ring + reticle + HUD panel overlays via
MapUtils.addTacticalOverlays(), and wire updateCount/updateReticle into
the SSE aircraft handler and drawRangeRings.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-13 22:35:43 +01:00
James Smith 5483ce5c3f 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 5e01e4152e feat: persist nav group open/closed state to localStorage
Adds initNavGroupState() and saveNavGroupState() functions so the
open/closed state of each .mode-nav-dropdown survives page reloads.
Active groups are never force-closed even if localStorage says closed.
Adds test_nav_state.py with two tests verifying presence of the
functions and data-group attributes on all five nav groups.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-13 21:08:27 +01:00