Backend support for these SDR types (SDRFactory/SDRType) was added in
prior commits, but the frontend hardware type selector was never
updated, so detected devices of these types could never be selected
and always showed as idle.
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>
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>
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>
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>
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>
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>
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>
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>
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>
- 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>
- 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>
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.
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>
* fix(adsb): disable bias-T on stop and warn when toggled while running
The RTL-SDR bias-T hardware register persists after the device is closed,
so toggling bias-T off in the UI and stopping the SDR had no effect on the
actual hardware — verified with a multimeter in issue #205.
- Add disable_bias_t_via_rtl_biast() to rtlsdr.py (mirrors enable, uses -b 0)
- Track adsb_bias_t_active in adsb.py; call disable on stop_adsb() so the
hardware register is cleared when ADS-B is stopped
- Show an inline warning in the UI when the bias-T checkbox is toggled while
any SDR mode is active, since the setting only takes effect at start time
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(lint): remove unused imports in tscm sweep.py
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Four list-trimming loops used querySelectorAll (static NodeList) inside a
while condition, so .length never decreased — causing infinite loops that
froze the page, or repeated removeChild calls on already-removed nodes
(TypeError: parameter 1 is not of type 'Node').
Also replaces blocking alert() with showInfo() for start errors and adds
a .catch() handler to the start_sensor fetch so network failures surface
cleanly instead of leaving the UI in a broken state.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Pager and sensor gain inputs changed from unvalidated text fields to
number inputs with min/max/step constraints
- ADS-B dashboard now exposes a gain input in the tracking strip;
previously gain was hardcoded to 40 dB with no user control
- validate_gain() ceiling raised from 50 to 102 dB to support HackRF
(LNA 40 + VGA 62 = 102 dB combined) and LimeSDR (73 dB)
- sdrCapabilities gain_max values corrected: HackRF 62→102, Airspy 21→45
- onSDRTypeChanged() now propagates gain_max to all mode gain inputs so
HTML constraints match the selected SDR's actual range
Closes#162
Adds a "Custom Range" sweep type that lets users specify start/end MHz
instead of using a fixed preset. Useful in dense RF environments where
a full or standard sweep returns too many signals and causes slowdown.
UI shows start/end MHz inputs when "Custom Range" is selected. Range is
validated (0 < start < end ≤ 6000 MHz) before the sweep starts.
Backend threads the ranges through to _scan_rf_signals(), which already
supports arbitrary frequency bands.
Closes#172
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.
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>
The updateHeaderClock function in index.html was inlined and still using
raw UTC (toISOString), while nav.html's version used InterceptTime.
Both ran on 1-second intervals updating the same element, causing the
clock to rapidly alternate between ET and UTC.
Fix: Updated the inline version in index.html to use InterceptTime,
matching nav.html. Added _navClockStarted guard and onChange listener
so only one interval runs and timezone changes apply instantly.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
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>
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>
- Access module-level _sweep_running, _current_sweep_id, and tscm_queue
via explicit package import to avoid UnboundLocalError from closure
variable shadowing in route handlers
- Remove orphaned tscmProgressBar.style.width assignment in index.html
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>