Commit graph

24 commits

Author SHA1 Message Date
e35aade819
feat(coverage): multi-box Coverage page (draw several areas, set-union) (#69)
Upgrades the Coverage page from one bbox to a list of named areas, matching
the backend coverage.areas / Shapely set-union gate. Draw multiple boxes,
name/edit/delete each, all rendered on the map; saves config.coverage.areas
(clears legacy bbox). Coords rounded to 6dp. Enabled + per-adapter override
toggles unchanged.

Co-authored-by: Matt Johnson <mj@k7zvx.com>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-06 12:42:36 -06:00
9ab2270884
feat(coverage): Leaflet Coverage page — draw one bbox that scopes every adapter (#65)
Adds a GENERAL > Coverage page: a Leaflet map to draw/set the universal
coverage bbox (with numeric W/S/E/N inputs), an enabled toggle, and per-
adapter override toggles (excluded_adapters). Environment's per-adapter
geographic-scope fields now show only when that adapter is overridden;
otherwise they point to the Coverage map. Saves via PUT /api/config/coverage.

Co-authored-by: Matt Johnson <mj@k7zvx.com>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-06 10:45:17 -06:00
2593e50aee
feat(meshcore): auto-add contacts so AIDA can DM anyone it hears (#55)
Enable firmware auto-add (set_autoadd_config CMD 58) at connect when
connection.meshcore_auto_add_contacts is set (default on), and refresh
the contact roster on NEW_CONTACT so replies resolve immediately. GUI
toggle on the MeshCore Connection page. So the USB AIDA companion adds
every node it hears an advert from and can send/decrypt DMs without
manual contact exchange.

Co-authored-by: Matt Johnson <mj@k7zvx.com>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-05 23:48:41 -06:00
7709fc1ec0
fix(gui): restart banner says 'Restart required' (process, not container recreate) (#54)
The banner said 'Container restart required' and a code comment claimed 'the
container will tear down' — both wrong. /api/restart touches /tmp/meshai_restart,
which the entrypoint watches to restart the BOT PROCESS in place (~seconds); the
container is not recreated. Fix the copy + point users at the existing 'Restart
now' button. Also mention transport connections in the restart-required list.

Co-authored-by: Matt Johnson <mj@k7zvx.com>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-05 23:13:10 -06:00
b1ebdd1434
feat(transport): MeshCore serial/USB + BLE support + USB auto-detect with stable paths (#52)
MeshCore can now connect over USB serial (and BLE) directly, not just TCP to
the pyMC companion. The meshcore lib already supported create_serial/create_ble;
we just wire it up. Plus a USB auto-detect scanner that resolves stable device
paths to fix ttyACM enumeration hopping across replug/reboot.

Backend:
- ConnectionConfig: meshcore_conn_type (tcp|serial|ble, default tcp),
  meshcore_serial_port, meshcore_baud=115200, meshcore_ble_address (validated)
- meshcore_transport._do_connect dispatches per mode: serial ->
  MeshCore.create_serial(port, baudrate, auto_reconnect, max_reconnect_attempts),
  ble -> create_ble(address or None), tcp -> create_tcp (unchanged). Mode-aware
  logging/reconnect. Transport otherwise unchanged (mode-agnostic once _mc exists).
- factory.meshcore_enabled(config): active when the selected mode is configured
  (serial port / ble address / tcp host); back-compat — meshcore_host + default
  tcp still activates exactly as before.
- serial_ports.list_serial_ports(): pyserial comports + stable_path resolution
  by-id -> by-path -> raw (by-id keyed on USB serial = stable across replug),
  likely_radio flag by VID (RAK/nRF/CP210x/CH340), excludes legacy ttyS*, never
  raises. GET /api/serial-ports (+ container by-id passthrough hint).

Frontend:
- SerialPortPicker component: "Detect USB devices" -> lists ports (likely-radio
  badge, shows stable_path) -> onChange sets the stable by-id path; manual text
  fallback; empty/error/note states.
- MeshCore Connection: type selector TCP/Serial/BLE + per-mode fields (serial
  picker + baud; ble address). Meshtastic serial branch now uses the picker too.

Code-ready; not activated (defaults keep TCP). 35 new tests; suite at 10-failure
baseline. Container needs /dev/serial passed through for by-id paths.

Co-authored-by: Matt Johnson <mj@k7zvx.com>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-05 22:12:03 -06:00
339d7e3c80
fix(gui): make 'API key required' banner reflect live /api/secrets status (#50)
The banner used a static META.hasKey flag (false for firms+roads511), so
those always showed 'API key not configured' even when keys were set —
contradicting the ManagedSecret SET badge. Now fetch /api/secrets and show
the banner only when a keyed adapter's secret is genuinely unset; copy
softened to 'API key required — set it in the field below'. wzdx kept keyless
(no banner). Keyless adapters never banner.

Co-authored-by: Matt Johnson <mj@k7zvx.com>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-05 19:33:01 -06:00
adfe9ff4f2
fix(gui): exclude timezone from SectionKey so section maps stay exhaustive (tsc) (#49)
The 100%-coverage pass added timezone to FullConfig, but it's a top-level
scalar (own PUT /api/config/timezone), not a section tab — so SectionKey =
keyof FullConfig broke SECTION_DESCRIPTIONS: Record<SectionKey,string>.
Exclude 'timezone' from SectionKey. Docker frontend build failed on this;
live container was untouched.

Co-authored-by: Matt Johnson <mj@k7zvx.com>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-05 19:25:35 -06:00
ad5509cb12
feat(gui): surface remaining config in the dashboard — 100% GUI coverage (#48)
Add bound GUI controls for every remaining user-facing + internal config
setting so the dashboard is the complete config surface (secrets stay in
.env via the ManagedSecret widgets). Per the exhaustive per-key audit.

19 GAP controls (real settings with no prior control):
- Config: global timezone (+ backend: timezone in VALID_SECTIONS + scalar
  save_section branch), commands.custom_commands (kv editor), knowledge
  sparse_host/port, alert_rules.high_util_hours
- Data Feeds: nws.areas, usgs.flood_thresholds (json), usgs_quake
  feed_url/min_magnitude(native floor)/bbox, wzdx.registry_ttl,
  satpass.broadcast_lead_seconds, central.connect_timeout,
  toggles.<family>.regions (un-hidden + save-merge fix)
- Notifications: band_conditions_tz, digest.schedule, digest.include
- Danger Zones: snow.enabled + buffer_mi (unlocked)

18 INTERNAL knobs (Advanced subsections):
- connection reconnect/timing (7) split across MT/MeshCore Connection
- environmental.geocoder url/timeout/radius/limit (4)
- identity.contact_email (+ LOCAL_FIELDS mirror -> local.yaml; still feeds
  NWS User-Agent); mesh_sources url + regions lat/lon already editable
- toggles.<family>.name/webhook_headers, danger_zones.webhook_headers
  (new reusable KeyValueInput component)

alert_node_ids intentionally not surfaced (synthetic local default with no
dataclass home; overlaps the editable per-rule node_ids). No dead controls.

Frontend validated at Docker build. Backend py_compile OK.

Co-authored-by: Matt Johnson <mj@k7zvx.com>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-05 19:22:55 -06:00
3495eb31de
feat(secrets): GUI-managed .env secrets store — keys are config, but gitignored (#47)
API keys/secrets now live in /data/secrets/.env (gitignored, never in config
YAML), while remaining fully editable from the dashboard. Config YAML holds
only ${VAR} references.

Backend:
- meshai/secrets_store.py: get_status (SET/NOT-SET, never values), set_secret,
  delete_secret over /data/secrets/.env (resolved like load_config); authoritative
  SECRET_FIELD_TO_ENV map (traffic→TOMTOM_API_KEY, firms→FIRMS_MAP_KEY,
  roads511→ROADS511_API_KEY, wzdx→WZDX_API_KEY, smtp→SMTP_PASSWORD,
  mesh_sources→MESHMONITOR_API_TOKEN) + backend-dependent llm_env_var
- dashboard/api/secrets_routes.py: GET /api/secrets (status only), PUT/DELETE
  /api/secrets/{env_var} (validated, restart_required); registered in server.py
- config_loader: save_section preserves ${VAR} secret refs on section save
  (never rejects them); EXPECTED_SECRETS += ROADS511_API_KEY, WZDX_API_KEY
- config.example.yaml + docker-entrypoint default config use ${VAR} refs;
  first-run bootstraps /data/secrets/.env; .gitignore covers it

Frontend:
- components/ManagedSecret.tsx: masked, Set/Not-set badge, reveal, Save->PUT,
  "restart required"; carries no config value so secrets never enter a section
  save payload
- wired into Environment (tomtom/roads511/wzdx/firms), Config LLM tab
  (env var by backend), Notifications (smtp)

Restart required after a secret change (env read at config-load). 11 store
tests; suite at 10-failure baseline (1714 passed).

Co-authored-by: Matt Johnson <mj@k7zvx.com>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-05 17:57:45 -06:00
5de683f7a7
chore: remove legacy TUI configurator; surface last config gap in GUI (#45)
Audit confirmed the Rich TUI (`meshai --config`) is a strict subset of the
dashboard GUI (covers FEWER sections) and nothing depends on it for bootstrap.
Strip it so the dashboard is the single config surface.

- delete meshai/cli/configurator.py (~1435 lines); cli/__init__ minimal
- main.py: drop --config + run_configurator dispatch (keep --config-file);
  bootstrap log now points to config.example.yaml / the dashboard
- docker-entrypoint.sh: remove the ttyd `meshai --config` block on :7682
  (default-config heredoc + bot exec loop untouched)
- docker-compose.yml + Dockerfile: drop the 7682 port + ttyd install (EXPOSE 8080)
- README / config.py header / config.example.yaml: de-reference the TUI
- drop `rich` from pyproject + requirements (grep-verified unused)

Close the only GUI config gap the audit found:
- Config.tsx weather tab: add openmeteo.url + wttr.url inputs (backend already
  round-trips the nested WeatherConfig dataclasses)

Bootstrap intact: config.example.yaml copy, entrypoint default-config write,
and defaulted Config() when no file exists all remain. `python3 -m meshai`
and `--config-file` unchanged. TS/import validated at Docker build.

Co-authored-by: Matt Johnson <mj@k7zvx.com>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-05 16:21:32 -06:00
6595b10bdd
feat(gui): native satpass + wzdx fields editable in dashboard; fix satpass enable layer (#40)
Close the GUI gaps so a standalone (all-native) deployment needs no
hand-editing of /data/config.yaml:

- Environment.tsx: native satpass (SGP4) section — observers editor
  ({slug,name,lat,lon,alt_m} list, mirrors traffic corridors), tle_groups,
  norad_ids, min_elevation_deg, window_hours, tle_refresh_seconds (all
  environmental.satpass YAML). Central adapter_config satpass panel unchanged.
- Environment.tsx: wzdx states + registry_url added (were file-only).
- BUG FIX: the satpass enable toggle wrote adapter_config.satpass.enabled
  (Central), but env/store.py gates the native adapter on
  environmental.satpass.enabled — toggle now writes the native/YAML layer.

Backend round-trips automatically (SatpassConfig/WZDxConfig already declare
all fields; _dict_to_dataclass whitelists by field). Frontend-only change.

NOTE: TS build (tsc && vite build) not runnable offline (node_modules absent);
validated at Docker build / deploy time.

Co-authored-by: Matt Johnson <mj@k7zvx.com>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-05 09:40:17 -06:00
70a0fd1657
feat(context): GUI control for chat-context retention (days) + live apply (#20)
- Config → Settings → Context: relabel the raw "Max Age (sec)" field to
  "Chat context retention (days)" (days<->seconds conversion, min 1,
  default 14). Governs the shared per-mesh chat memory window.
- Make PUT /api/config/context apply LIVE: MeshContext.update_settings()
  updates max_age/observe_channels/ignore_nodes in place; config_routes
  refreshes the running MeshContext via app.state.mesh_context (mirrors
  the existing _refresh_toggle_filter pattern) so retention changes take
  effect without a restart.

Tests: +tests/test_context_hot_reload.py (10); 0 new failures.

Co-authored-by: Matt Johnson <mj@k7zvx.com>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-03 19:03:37 -06:00
a191200a12
feat(meshcore): decouple per-mesh LLM DM gate + mesh-scoped chat context (#19)
- router.should_respond branches on message.transport: MeshCore DMs are
  governed solely by meshcore_context.respond_to_dms (enforced at the
  transport); Meshtastic solely by bot.respond_to_dms. No global gate.
- MeshObservation tagged per-transport; the LLM "recent traffic" block is
  scoped to the originating mesh (keyword override for the other mesh),
  labeled by mesh so the model knows which it is describing.
- MeshCore observe_channels is now opt-in (empty = observe none).
- Chat-context retention 30d -> 14d (both meshes).
- Meshtastic integer channel-index filter no longer misapplied to MeshCore
  observations (their channel is a companion slot index).
- Frontend: relabel DM toggles per-mesh ("Answer direct messages",
  Meshtastic-only / MeshCore-only), remove the false channel-mention
  tooltips, opt-in wording for MeshCore observe-channels.

Tests: +tests/test_llm_scoping.py (10), context-filter updated for opt-in;
0 new failures (34 pre-existing).

Co-authored-by: Matt Johnson <mj@k7zvx.com>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-03 18:39:26 -06:00
284fb5cbf2
MeshCore Contacts roster + Companion status (read-only) (#17)
* feat(dashboard): MeshCore Contacts roster + Companion status (read-only)

Expose the live companion's contact roster (get_contacts) and self/channel
status via /api/meshcore/contacts + /api/meshcore/self. Fill the Contacts
(roster table) and Companion (status + channels) pages. Telemetry auto-poll
comes next.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(meshcore): self-advertisement (send-advert + advert-on-connect + periodic)

AIDA now announces itself: send_advert(flood=True) on every connect, an
optional periodic auto-advert (meshcore_advert_interval_seconds), and a
manual "Send Advert" button + POST /api/meshcore/advert. Makes the
companion discoverable/DM-able on the mesh.

---------

Co-authored-by: Matt Johnson <mj@k7zvx.com>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-03 16:14:10 -06:00
0460462485
Phase A — 4-section nav; move Scheduled Broadcasts + Danger Zones off Routing (#15)
* feat(dashboard): Phase A — 4-section nav; move Scheduled Broadcasts + Danger Zones off Routing

Regroup nav into GENERAL/MESHTASTIC/MESHCORE/DOCUMENTATION (<=5 pages each,
MT & MC mirror). Consolidate via tabs (Places, Nodes & Health, Contacts &
Companion) reusing existing components. Move Band Conditions, cold-start,
and fire digest to per-mesh Scheduled Broadcasts pages; move Danger Zones
to its own page. Routing keeps its sending rules unchanged.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(dashboard): Phase B — clean identical Routing grids; relocate per-family gating to Data Feeds

Meshtastic Routing becomes an always-visible pure-delivery grid matching
MeshCore (no master-toggle expand/collapse). Per-family gating (enable/
severity/freshness/cooldown) moves to a Family Settings section on Data
Feeds. Sending rules + Notification Rules unchanged.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(dashboard): Phase C — MeshCore bot-behavior parity (observe channels / ignore contacts / DMs)

Add meshcore context (observe channels by name, ignore contacts, DM policy)
and wire the MeshCore inbound path to honor it, mirroring Meshtastic's
observe/ignore filtering. Symmetric "Bot behavior" sections on both
Connection pages. Meshtastic path unchanged.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(dashboard): Phase D — dedupe Environment/Adapter Config into one Data Feeds surface

Curated family panels are the single home for the shared adapter keys;
Adapter Config becomes an Advanced/raw escape hatch (owned keys no longer
double-editable). Surface include_in_llm_context per adapter. Fix the
adapter-config array-vs-object parsing (fire digest values now load).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(dashboard): Phase E — Activity Log (per-mesh broadcast feed); remove subscription backend

Replace Alerts with an Activity Log fed by per-mesh broadcast logging
(transport+channel+success on mesh_broadcasts_out, additive migration).
Remove the entire subscription backend (commands, DM dispatch, storage,
API) and its UI.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Matt Johnson <mj@k7zvx.com>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-03 14:57:24 -06:00
11bac716d0
feat(dashboard): send-test-message + MeshCore channel list (#14)
Add POST /api/mesh/test-send (fire a labeled test broadcast on a chosen
mesh+channel via the live transport) and GET /api/meshcore/channels
(surface the companion's enumerated channel names). "Send test message"
cards on both Connection pages, with the MeshCore one listing real
channels. Lets the operator confirm a mesh's send path on demand.

Co-authored-by: Matt Johnson <mj@k7zvx.com>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-03 01:06:31 -06:00
47b56adab5
refactor(transport): derive active transports from config, drop transport setting (#13)
* refactor(transport): derive active transports from config, drop transport setting

A mesh is active when its connection is configured: Meshtastic is the
always-on base; MeshCore runs whenever meshcore_host is set (blank = off);
both configured = both. Removes the transport mode field/toggle entirely
so there's no separate flag to miss.

* docs: fix stale transport comment after field removal

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Matt Johnson <mj@k7zvx.com>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-03 00:18:53 -06:00
b260dcbae0
fix(dashboard): restart URL, Enable-MeshCore toggle, unsaved-changes guard (#12)
* fix(dashboard): restart URL, Enable-MeshCore toggle, unsaved-changes guard

- RestartBanner POSTs /api/restart (was /api/system/restart -> 405)
- MeshCore Connection: replace transport-mode dropdown with an
  Enable MeshCore toggle (on=both, off=meshtastic)
- Guard unsaved edits: confirm before navigating away with pending
  changes (config pages no longer silently discard edits)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* docs(dashboard): drop stale "Transport mode" wording on MeshCore Connection header

---------

Co-authored-by: Matt Johnson <mj@k7zvx.com>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-02 23:41:56 -06:00
de1e58aa71
feat(routing): MeshCore as first-class delivery types (meshcore_broadcast/dm) (#11)
* feat(routing): MeshCore as first-class delivery types (meshcore_broadcast/dm)

Replace the composite auto-fan with explicit per-mesh delivery types so
each family independently controls broadcast/DM per severity on Meshtastic
AND MeshCore. mesh_broadcast->Meshtastic only, meshcore_broadcast->MeshCore
(by channel name), mesh_dm/meshcore_dm likewise; routing via the existing
transport hint. Adds meshcore_dm_contacts. Meshtastic-only configs
unchanged.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(routing): deliver meshcore_broadcast via meshcore_channel through CompositeTransport

The hinted _broadcast path passed the channel NAME on the `channel` kwarg,
which MeshCoreTransport ignores (it reads meshcore_channel), so
meshcore_broadcast silently no-op'd on transport=both configs. Route the
meshcore child via meshcore_channel and the meshtastic child via channel.
Fix the test that asserted the broken kwarg layout. Add the new delivery
types to the remaining enumeration/validation sites (channel-test endpoint,
scheduler digest chunking, danger-zone valid set).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(dashboard): split Notifications into Meshtastic and MeshCore sections

Delineate per-mesh routing: each family configures Meshtastic delivery
(mesh_broadcast/mesh_dm, channel index, node IDs) and MeshCore delivery
(meshcore_broadcast/meshcore_dm, channel name, contacts) in separate
sections; shared settings (enable/severity/regions/email/webhook) once.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(dashboard): first-class MeshCore nav section + dedicated pages

Group the sidebar into Meshtastic and MeshCore sections. Promote MeshCore
routing and connection to their own pages; move MeshCore routing out of
Notifications (which stays Meshtastic + shared family settings). Add
placeholder Contacts and Companion pages for the follow-on companion data
API. No backend change.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(dashboard): parallel MT/MC nav order + symmetric connection links

Order both nav groups Connection/Routing/Mesh(Contacts)/Sources(Companion).
Replace the prominent MeshCore block on the Meshtastic Connection page with
a single subtle cross-link, mirrored on the MeshCore Connection page.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(dashboard): focused Meshtastic Connection/Sources pages for MT/MC parity

Meshtastic Connection and Sources are now their own focused pages
(mirroring MeshCore), instead of deep-linking into the full Config page.
Global settings move to a restored top-level Config item. No duplicate
editors; connection cross-links are mirror-image between the two pages.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Matt Johnson <mj@k7zvx.com>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-02 22:42:14 -06:00
24cb6a31df
feat(dashboard): MeshCore transport + per-family routing GUI controls (#10)
* feat(dashboard): MeshCore transport + per-family routing GUI controls

Add Transport mode selector (Meshtastic/MeshCore/Both) and MeshCore
host/port fields to the Config Connection section, and an independent
per-family "MeshCore channel" number input in Notifications (blank = not
broadcast on MeshCore, sends null). Extends the ConnectionConfig and
per-family toggle TS types.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* refactor(routing): MeshCore routing by channel name, not index

MeshCore channels are {name,PSK} (up to 40+ slots, not Meshtastic's 0-7).
The send index is a fragile slot position, so store the channel NAME per
family and resolve name->slot against the companion's live channel table
at send time; never blind-send to an unresolved slot. GUI field becomes a
channel-name text box. meshtastic path unchanged.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(routing): thread per-family meshcore_channel through the broadcast send path

MeshBroadcastChannel now carries the rule's meshcore_channel name and
passes it to send_message, so per-family MeshCore routing actually fires
end-to-end (dispatcher -> channel -> composite -> MeshCoreTransport).
Meshtastic path unchanged.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Matt Johnson <mj@k7zvx.com>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-02 16:49:59 -06:00
Ubuntu
e982cbbdf6 refactor(danger-zones): drop min_severity + position_max_age; table snow (grayed/inert)
- alert on any hazard touching a node (severity gate removed)
- alert regardless of position staleness; only skip nodes with no position
- snow tabled: grayed-out in GUI + skipped in correlator pending snowfall+elevation pipeline

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-21 06:56:22 +00:00
Ubuntu
95b1a23ef1 feat(danger-zones): configurable infra-node hazard correlation + fire age-gate
- danger_zones config section (isolated dataclass + danger_zones.yaml + GUI panel on Notifications page); defaults disabled + dry_run
- DangerZoneCorrelator: distance-based correlation of hazard events vs infra nodes (CLIENT_BASE/ROUTER/ROUTER_LATE), DM delivery, cooldown
- fire age-gate (wfigs max_declare_age_seconds) suppresses stale/closed fires announced as "New"
- tests: correlator + fire-gate boundary; wfigs fixture fix

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-21 06:33:54 +00:00
Ubuntu
6e3eae39fb fix(ci): add package-lock.json for npm ci in Docker build
npm ci requires a lockfile. Generated from existing package.json.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-06-16 03:48:44 +00:00
Ubuntu
2e1fb325f7 refactor: move source tree into work/, multi-stage Docker build, fix satpass
- Move all application source (meshai/, dashboard-frontend/, tests/,
  config/, docs/, Dockerfile, etc.) into work/ directory
- Add Node.js multi-stage build to Dockerfile for frontend compilation;
  remove compiled static assets from git tracking
- Fix satpass missing time windows: consolidation was splitting wire on
  newline and only putting line 1 in event.title, dropping the time
  window line that the composer uses for precomposed broadcasts
- Fix satpass burst flooding: stagger consolidation timers (+60s per
  pending pass) so Central batch publishes don't blast the mesh
- Update CI workflow build context to work/
- Anchor lib/ and data/ gitignore patterns to repo root to prevent
  false matches on nested directories
- Add dashboard-frontend/node_modules/ to .dockerignore

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-06-16 03:40:31 +00:00