The system metrics collector daemon thread ran vcgencmd via subprocess
every 3s even on non-Pi hosts, where it always failed — and leaked
Popen calls into any later test mocking subprocess (intermittent
test_weather_sat_decoder failure in full-suite runs).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
confidence_boost and the manufacturer-data-length signal applied without
any identifying indicator match, giving every device a phantom AirTag
baseline (a 22+ byte payload from any vendor scored 0.30 and was flagged
as an AirTag). Both now require a matched indicator, mirroring the
score>0 gating already used in _check_generic_tracker_indicators.
Name-pattern weight raised 0.15 -> 0.30 so a device advertising a known
tracker name yields a LOW-confidence detection, consistent with the
TSCM BLE scanner's name-only detection and the engine docstring.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Bluetooth aggregator/api/heuristics tests updated to current behavior;
deauth detector integration test rewritten to exercise the tracker and
alert path directly instead of patching __globals__ (read-only on
Python 3.14).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Matches the agent's established try/except import convention; the
agent now starts and reports empty capabilities when utils.capabilities
(or its dependency chain) is unavailable.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Caught by test_dependency_files_integrity, which had been buried in the
never-reached tail of the test suite.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Phase 5 decision gate of the architecture refactor plan: dedicated
dashboard pages are the pattern for map-centric modes; APRS and
Meshtastic migrations to follow under separate plans.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
detect_mode_availability accepts a pre-computed dep_status so the agent
probes once; interface and fallback paths now have content-level tests.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
utils/capabilities.py now owns interface detection and mode
availability; the agent delegates via detect_interfaces() and
detect_mode_availability(). The agent keeps config gating and
tool_details population to preserve its result shape exactly.
The moved fallback path uses utils.dependencies.check_tool instead of
the agent's old shutil.which fallback; check_tool also searches
Homebrew paths, a strict superset (strictly better detection).
Co-Authored-By: Claude Fable 5 <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>
requests/urllib3 collapse dot segments before sending, so traversal
like wifi/v2/../../x escaped the prefix allowlist. Only canonical
paths are now forwarded; regression tests included.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Removes the one-off proxy_wifi_clients route and the dead getApiBase()
helper; the allowlisted passthrough now covers agent wifi traffic.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Removes the agent's own CelesTrak download (the source of the stray
gp.php artifact) — the store is now the single TLE source for app and
agent alike.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Both silently fell back to static bundled TLEs after the removal of
routes.satellite._tle_cache.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
_resolve_satellite_request now operates on a caller-provided dict and
accumulates write-backs, flushed once per request behind a guard —
avoids per-satellite full-dict copies and store-cache thrash, and a
transient DB error can no longer fail a read request.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
data/satellites.py is no longer rewritten at runtime; it remains as
the read-only seed for the store.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- busy_timeout so concurrent app+agent writers wait instead of raising
- seed from _connect() so update-before-first-read can't drop the seed
- regression tests: seed ordering, concurrent writer, default DB path
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- document str defaults / bytes for binary-mode callers
- wire __exit__ to False so exceptions are not suppressed
- exercise exited-process path through subprocess.run
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>
Planespotters.net now requires a descriptive User-Agent with a contact
URL or email — generic strings return 403. Updated to comply with their
API policy.
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>
SensorDashboard.applyViewState was resetting output.style.display=''
in the else branch, undoing switchMode's modesWithVisuals hide for
waterfall, morse, ook, and every other mode with its own visuals.
Only touch #output when mode === 'sensor'.
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>
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>
GitHub Actions ubuntu-latest has 7GB RAM. Running all 1362 tests in
a single process exhausts it (~9 min, runner shutdown signal). Split
into two matrix jobs (test_[a-l] and test_[m-z]) so each job starts
with fresh memory, halving peak usage.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- pyproject.toml: sync missing deps (flask-wtf, flask-compress,
simple-websocket, gunicorn, gevent, psutil, cryptography, meshcore,
pre-commit) so test_requirements integrity check passes
- tests/conftest.py: set INTERCEPT_DISABLE_AUTH=1 so auth routes
return 200 instead of 302 in tests
- routes/bluetooth_v2.py: add device_to_dict() helper that flattens
heuristics to top level for test_bluetooth_api serialization tests
- utils/bluetooth/heuristics.py: evaluate() now returns the device so
callers can chain; was returning None
- tests/test_satellite.py: reduce hours 48→2 in pass-prediction test
to prevent OOM kill on GitHub Actions 7GB runner at the 59% mark
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>