- 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>
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.
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>
- 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 three new icon shapes (widebody, bizjet, turboprop) to the existing
set (jet, prop, helicopter, military, glider), giving 8 distinct silhouettes.
Classification covers common ICAO type codes: widebodies (744, 777, A380 etc.),
business jets (Citation, Gulfstream, Learjet etc.), turboprops (ATR, DH8 etc.),
and light GA piston aircraft.
Hover tooltip now shows aircraft type description (e.g. "Airbus A320-200")
when available from the aircraft DB, in addition to callsign and altitude.
Closes#201
Timezone fixes:
- Add utils.js (InterceptTime) to adsb_dashboard.html — was completely
missing, causing all times to fall back to UTC regardless of setting
- Register onChange listener in nav.html so clock updates instantly
when timezone/format is changed in Settings
- Initialize timezone/format dropdowns on ADS-B dashboard page load
- Browser-verified: ET/12h ↔ UTC/24h switches instantly on ADS-B page
VDL2 correlation fix:
- Force ICAO hex to uppercase when promoting from VDL2 src.addr (dumpvdl2
may output lowercase, ADS-B stores uppercase — case mismatch prevented
correlator from matching)
- Move ICAO/addr promotion before ACARS field extraction so even
non-ACARS VDL2 frames (XID, connection mgmt) get correlated
Auth:
- Add INTERCEPT_DISABLE_AUTH env var to skip login for local/dev use
- Configurable via docker-compose.yml environment
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Root cause: dumpvdl2 outputs nested JSON (vdl2.avlc.acars.flight) but
FlightCorrelator only checks top-level fields. VDL2 messages were stored
in the correlator but never matched to any aircraft.
Fix: Promote identifying fields (flight, reg, tail, icao, addr, label,
text) from the nested VDL2 structure to top-level before storing in the
correlator. Also promote AVLC source address as ICAO when src.type is
"Aircraft".
Also fix VDL2 sidebar timestamps to use global InterceptTime setting.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Use global InterceptTime for all ACARS timestamps (respects Eastern/12h)
- Add weather message rendering (wind, temperature, turbulence)
- Add CPDLC controller-pilot message rendering (purple highlight)
- Add squawk code change rendering (red highlight)
- Fix engine_data crash when parsed value isn't an object
- Show tail/registration alongside flight number on all cards
- Increase message text truncation to 200 chars
- Add FL prefix to flight level in position reports
- Applied consistently across ADS-B dashboard, sidebar feed, and standalone ACARS mode
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
When using a remote SBS feed, no local SDR is needed. The pre-flight
device conflict check was running regardless and stopping whichever
mode had the selected SDR device claimed — even though ADS-B remote
mode never touches a local SDR. Skip the conflict check when remoteConfig is set.
Pass DEFAULT_LATITUDE/DEFAULT_LONGITUDE from config to both standalone
dashboard templates so observer-location.js uses .env values instead of
falling back to hardcoded London coordinates on first visit.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Fix SSE fanout thread AttributeError when source queue is None during
interpreter shutdown by snapshotting to local variable with null guard
- Fix branded "i" logo rendering oversized on first page load (FOUC) by
adding inline width/height to SVG elements across 10 templates
- Bump version to 2.26.0 in config.py, pyproject.toml, and CHANGELOG.md
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The CSS ::after dot positioning was unreliable across fonts and sizes.
Switch to an inline SVG of the "i" glyph (green dot + cyan stem/bars)
extracted from the logo — renders pixel-perfect at any size.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The dotless i (ı) wasn't rendering in all fonts. Switch to a regular "i"
with the green dot CSS overlay positioned on top of the native dot.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Matches the logo icon — the "i" in iNTERCEPT now renders with a cyan
letter and green dot via CSS, consistent across the main header, welcome
card, dashboard headers, help modal, settings modal, and all popout pages.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
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>
The airband start function was calling parseInt() directly on composite
device selector values like "rtlsdr:0", which always returned NaN and
fell back to device 0. This also meant sdr_type was never sent to the
backend, and could result in int(None) TypeError on the server.
Now properly splits the composite value (matching ADS-B/ACARS/VDL2
pattern) and sends both device index and sdr_type. Also hardened
backend int() parsing to use explicit None checks.
Fixes: "Airband Error: Invalid parameter: int() argument must be a
string, a bytes-like object or a real number, not 'NoneType'"
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
When the /devices fetch hasn't completed or fails, parseInt on an empty
select returns NaN which JSON-serializes to null. The backend then calls
int(None) and raises TypeError. Fix both layers: frontend falls back to
0 on NaN, backend uses `or` defaults so null values don't bypass the
fallback.
Also adds a short TTL cache to detect_all_devices() so multiple
concurrent callers on the same page load don't each spawn blocking
subprocess probes.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
When navigating from another mode (e.g. pager) to the ADS-B dashboard,
the old process could still hold the USB device. Two fixes:
1. routes/adsb.py: If dump1090 starts but SBS port never comes up,
kill the process and return a DEVICE_BUSY error instead of silently
claiming success with no data.
2. templates/adsb_dashboard.html: Pre-flight conflict check in
toggleTracking() queries /devices/status and auto-stops any
conflicting mode before starting ADS-B, with a 1.5s USB release
delay.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Escape ac.icao, callsign, typeCode with escapeHtml() in aircraft card (XSS)
- Add linking comments between duplicated IATA_TO_ICAO mappings
- VDL2 sidebar: single-click selects aircraft, double-click opens modal
- Remove stale ICAOs from acarsAircraftIcaos in cleanupOldAircraft()
- Add null guard to drawPolarPlot() in weather-satellite.js
- Move deferred imports (translate_message, get_flight_correlator) to module level
- Check all frequency checkboxes by default on initial load
- Remove extra blank lines and uncertain MC/MCO airline code entry
- Add TODO comments linking duplicated renderAcarsCard implementations
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Resolved conflicts:
- routes/acars.py: keep /messages and /clear endpoints for history reload
- routes/vdl2.py: keep /messages and /clear endpoints for history reload
- templates/adsb_dashboard.html: keep removal of hardcoded device-1
defaults for ACARS/VDL2 selectors (users pick their own device)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The registry used plain int keys (device index), so HackRF at index 0
and RTL-SDR at index 0 would collide. Changed to composite string keys
("sdr_type:index") so each SDR type+index pair is tracked independently.
Updated all route callers, frontend device selectors, and session restore.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Clear sidebar highlights and ACARS message timer when stale selected
aircraft is removed in cleanupOldAircraft()
- Escape all user-controlled strings in renderAcarsCard(),
addAcarsMessage(), and renderAcarsMainCard() before innerHTML insertion
- Remove dead duplicate H1 check in classify_message_type
- Move _d label from link_test set to handshake return path
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Resolved conflict in static/js/modes/weather-satellite.js:
- Kept allPasses state variable and applyPassFilter() for satellite pass filtering
- Kept satellite select dropdown listener for filter feature
- Adopted upstream's optimistic stop() UI pattern for better responsiveness
- Kept optional chaining (pass?.trajectory) since drawPolarPlot can receive null
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>