Commit graph

23 commits

Author SHA1 Message Date
ae884b9651 feat: avalanche multi-line wire format, danger-level re-emit, GUI panel
- store.py: add avalanche-specific elif block with danger_level rise
  detection; re-emit on level increase with _is_update flag
- avalanche.py: rewrite to_event() with multi-line wire format
  (ski emoji + New:/Update: prefix, zone, danger name/level,
  travel advice, center_id), min_danger_level floor from adapter_config
- defaults.py: add (avalanche, min_danger_level) to REGISTRY (default=3)
- Environment.tsx: structured avalanche panel with broadcast settings
  section, min danger level select (3-Considerable/4-High/5-Extreme)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-06-09 04:00:47 +00:00
fe6589e0e5 feat: quake multi-line wire format + GUI panel wired to real adapter_config keys
A) _render() now emits multi-line format matching Fire/Roads style:
   emoji prefix M{mag} — place / Depth · coords / TSUNAMI WARNING
B) Environment.tsx usgs_quake panel replaced — dead min_magnitude/bbox
   controls removed, wired to real adapter_config keys: global_mag_floor,
   regional_mag_floor, regional_radius_mi, escalate_mag_floor,
   broadcast_pager_alerts.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-06-09 03:38:46 +00:00
96f14afba8 refactor: promote WZDx to first-class adapter with own config namespace
Move work zone settings out of itd_511 into dedicated wzdx adapter:
- config.py: add WZDxConfig dataclass with feed settings
- defaults.py: migrate 3 work_zone keys to wzdx namespace (broadcast,
  min_severity, sub_types) + add ADAPTER_META entry
- incident_handler.py: work zone gate reads adapter_config.wzdx
- Environment.tsx: full WzdxConfig state/load/save/discard, native feed
  fields when feed_source!=central, broadcast settings panel

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-06-08 21:33:38 +00:00
dc64430394 dashboard: split WZDx Work Zones into dedicated Roads sub-tab
Add 'wzdx' adapter key with its own META entry and render block.
Move work zone controls (enable toggle, min severity, sub-types)
out of the roads511 panel into the new WZDx tab. Data still
loads/saves via /api/adapter-config/itd_511 using the existing
roads511Config state. The wzdx panel mirrors roads511 enabled and
feed_source since they share the same backend adapter.

Bundle: D045j2lq -> BiMKNe5L.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-06-08 21:01:36 +00:00
53decde03c itd_511: add configurable work zone broadcast gate + dashboard controls
defaults.py: add work_zone_enabled (bool, default false),
work_zone_min_severity (str, default Minor), work_zone_sub_types
(json, default [road_works, lane_closed, road_closed]) to itd_511.

incident_handler.py: replace hardcoded work_zone return None with
adapter_config-driven gate. Resolve sub_type and event_sev before
the work_zone check so severity and sub-type filters apply. Non-work-zone
events keep the existing min_severity / enabled_categories / enabled_sub_types
filters unchanged.

Environment.tsx: add work_zone_enabled, work_zone_min_severity,
work_zone_sub_types to Roads511Config. Load/save/discard wired. Work Zones
section in roads511 panel with enable toggle, min severity dropdown, and
sub-type checkboxes (visible only when enabled).

Bundle: KLGUZQYL -> D045j2lq.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-06-08 18:18:39 +00:00
31c464c0ee dashboard: add NWS broadcast filter controls to Environment page
Add NwsConfig adapter config (broadcast_severities, duplicate_allowed_after_seconds)
with load/save/discard/change-detection wiring. When feed_source=central, hide
native-only fields (User Agent, Tick Seconds) and show Broadcast Filters section
with severity checkboxes and re-broadcast cooldown input.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-06-08 14:55:05 +00:00
49de5f3a86 feat(incident): add ITD 511 severity, category, and sub-type filters
Backend:
- Add itd_511.min_severity (str, default None), enabled_categories
  (json, default [incident, closure]), enabled_sub_types (json,
  default 7 common types) to adapter_config registry.
- Wire _parse_itd_511_incident to gate on all three: severity
  ordering (None < Minor < Major), category whitelist, sub-type
  whitelist (empty = all pass).

Dashboard:
- Add Roads511Config state + API load/save/discard for itd_511.
- Add Broadcast Filters section to the 511 Road Conditions panel:
  severity dropdown, category checkboxes, sub-type checkboxes.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-06-08 05:03:23 +00:00
1e6d22ecfe feat(dashboard): add TomTom broadcast filter knobs to traffic panel
Add min_magnitude dropdown (1-4), drop_non_present and
drop_zero_magnitude toggles to the TomTom Traffic adapter card.
State loads from /api/adapter-config/tomtom_incidents on mount
and saves changed keys on save, following the same pattern as
the WFIGS and fires config panels.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-06-08 03:55:43 +00:00
fa8553a1e0 refactor(dashboard): move Central Connection to dedicated tab
- Add 'Central' as first tab in FAMILIES array (before Weather)
- Import Server icon from lucide-react
- Remove Central Connection card from header area
- Render Central config in its own tab panel (no adapter sub-tabs)

The Central tab now shows URL, Durable, and Region fields with the
enabled toggle, matching the previous inline card behavior.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-06-06 20:18:34 +00:00
9d8d5fd321 feat(dashboard): expanded fire config with incident types and triggers
- Add allowed_incident_types (WF/RX/OTHER) checkboxes
- Conditionally show native polling settings (tick_seconds, state)
- Broadcast triggers section with acres/containment toggles
- Cooldown and freshness window inputs in hours

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-06-06 19:27:12 +00:00
335dbf632c feat(dashboard): fire adapter config in Environment page
Rework the NIFC Fire Perimeters tab to show all fire config in one place:
- Native polling settings (tick_seconds, state) shown only when native
- Broadcast Settings section (always shown):
  - Broadcast on acres increase toggle
  - Broadcast on containment increase toggle
  - Update cooldown (hours, converted from cooldown_seconds)
  - Freshness window (hours, 0 = disabled)
- Daily Digest section (always shown):
  - Digest enabled toggle
  - Schedule times (HH:MM list)

Changes persist to adapter_config table via PUT /api/adapter-config/{adapter}/{key}.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-06-06 19:04:29 +00:00
798712d20c docs(v0.7): comprehensive dashboard docs rewrite -- Reference +8 sections, per-page tooltips, component polish
All three approved tiers in one commit. Reference.tsx is the deep docs
hub (8 new sections); the 10 other pages get short helper text +
tooltips that cross-reference back into Reference; 3 components get
operational-context tooltips. No new features land here -- this is the
copy that catches the GUI up to v0.6 + v0.7 system behavior.

Decisions applied per Matt's call:
- Keep both bang commands AND the LLM DM path (bangs are short on a
  mesh-constrained interface; LLM is the anything-else path). Cross-
  references between the two land in Reference -> Commands and
  Reference -> LLM DM Queries.
- Rename "wire-string rendering" to "broadcast text" in user-facing
  copy on TownAnchors.tsx, GaugeSites.tsx, and the Curation section of
  Reference.tsx.
- Keep the "AND-model anti-pattern" tooltip as-is on Environment.tsx +
  GaugeSites.tsx (specificity is the value for advanced users); the
  OR-not-AND Reference section is its home definition that other
  tooltips can link to.

Ham terminology preserved:
- Reference.tsx solar/Kp section retains "Quiet sun" / "Quiet HF
  conditions" language (SFI/Kp vocabulary, not the deleted Quiet Hours
  feature -- confirmed via direct grep before writing).

Tier 1: Reference.tsx (the depth doc) -- 8 new sections, ordered for
readability:

- "Fire Tracker (Fusion)": Phases 1-4 unified. Six fire-family alert
  categories with example wire strings (wildfire_declared,
  wildfire_growth, wildfire_halted, wildfire_spotting,
  unattributed_hotspot_cluster, wildfire_incident). Attribution
  mechanics (spread_radius_mi default, centroid as 24h median).
  Movement mechanics (pass_id bucketing, per-pass centroid, 8-way
  bearing, mi/h drift). Spotting mechanics (convex-hull perimeter +
  vertex-distance approximation + per-fire cooldown). Daily LLM digest
  (twice-daily summary broadcaster). The 10 fires.* adapter_config
  knobs with defaults.
- "Broadcast Types": the three prefix categories -- New: (first sight),
  Update: (material change), Active: (clock-driven reminder).
- "Reminder System": cadences per adapter (WFIGS 8h, SWPC 8h, ITD 511
  per-zone). The tombstone (fires.tombstoned_at) termination. The
  per-adapter reminder_enabled flag.
- "LLM DM (Natural-Language Queries)": all 7 env_reporter adapter
  blocks (build_fires_detail / build_alerts_detail / build_quakes_detail
  / build_traffic_detail / build_gauges_detail / build_swpc_detail /
  build_drop_audit) with example questions that hit each one. The
  grounding clause behavior ("No active X right now" when an adapter
  block is empty -- the v0.7-fire-tracker-4-final clamp). The
  include_in_llm_context per-adapter toggle.
- "OR-not-AND Architecture": the per-adapter Central vs native
  contract. Mutually exclusive. The AND-mode anti-pattern definition
  (referenced by the Environment + GaugeSites tooltips). The Spokane
  fix context.
- "Adapter Config & the CODE Rule": the GUI knob hub. The CONFIG-vs-
  CODE split (thresholds in CONFIG, sentence templates / emoji /
  translation maps in CODE). Restart-required vs live keys. The
  include_in_llm_context toggle.
- "Curation: Gauges & Towns": Gauge Sites (NWS-AHPS thresholds, USGS
  lookup, Action/Minor/Moderate/Major). Town Anchors (broadcast text
  suffix lookup chain: Photon -> this table -> landclass -> county
  -> coords). Example output "3 mi N of Almo".
- "Schema Migrations": light touch. v11-v16 schema additions tagged
  with the phase they shipped under.

Tier 2: per-page tooltips and cross-references (10 pages):

- AdapterConfig.tsx: header paragraph extended with the CODE rule
  pointer + LLM context toggle explanation.
- Alerts.tsx: !subscribe blurb extended with the three broadcast types
  and links to Reference -> Broadcast Types + Reminder System.
- Config.tsx: environmental section description updated to point at
  Environment.tsx for adapter knobs + Reference -> OR-not-AND for the
  architecture.
- Dashboard.tsx: RF Propagation title carries SWPC R/S/G + Kp legend
  tooltip; LOCAL badge defines what counts as local.
- Environment.tsx: Central region-token helper now references the
  OR-not-AND section; tick_seconds defined inline as the native-mode
  poll interval.
- GaugeSites.tsx: page description rewritten -- replaces "envelope
  time" jargon with operational language, explains USGS lookup
  mechanics, points at Reference -> OR-not-AND for the central-feed
  disable.
- Mesh.tsx: Topology + Geographic buttons get tooltips defining the
  rendering model.
- Notifications.tsx: band-conditions block extended with the daily
  fire digest pointer + Reference -> Fire Tracker + Broadcast Types
  cross-refs.
- TownAnchors.tsx: page description rewritten -- "wire-string
  rendering" -> "broadcast text", chain fallback explained ("Photon
  -> this table -> landclass -> county/state -> coords"), example
  output included.

Tier 3: component tooltip polish (3 components):

- NodeTable.tsx: Battery + Last Heard column headers get title-bearing
  spans with the voltage chart + offline-threshold legend.
- NodeDetail.tsx: SNR quality bands documented as a comment in the
  neighbor render block (the legend lives next to where the colored
  quality dots are computed).
- RestartBanner.tsx: banner copy extended with the restart-required
  catalog (Config -> environmental, LLM backend swap, dispatcher
  cold-start grace) so operators know what touched it.

Build verification:
- tsc + vite build green (one warning about chunk size > 500kB --
  pre-existing).
- All 8 new TOPICS ids resolve in the served bundle:
    adapter-config, broadcast-types, curation, fire-tracker,
    llm-dm, or-not-and, reminders, schema.
- Distinctive new strings present in the bundle ("3 mi N of Almo",
  "Photon nearest-town", "AND-mode anti-pattern", "R (Radio Blackouts").
- "Quiet sun" preserved (the ham SFI/Kp vocabulary in the Solar
  section, not the deleted Quiet Hours feature).
- Container Up healthy, 0 tracebacks in 2 min post-rebuild.

Changelog: v0.7-docs-rewrite.md (per-page strip / rewrite / add table).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-06-06 15:24:34 +00:00
matt+claude
c2d5bcfbd1 feat(central): v0.5.4 -- region-aware subscriptions using Central v0.9.20 regional subjects
Pre-v0.5.4 every Central subscription used a bare wildcard (central.wx.>,
central.fire.>, central.traffic.>, central.quake.>, central.hydro.>,
central.space.>), so a Magic Valley operator flipping nws -> central was
in fact subscribing to the all-US firehose and discarding 95% of events
locally. Central v0.9.20 (2026-05-28) added per-region subject suffixes
so the firehose can be filtered server-side. This wires meshai to use them.

Backend (meshai/central/consumer.py):
- New _subjects_for(adapter, region) replaces the static ADAPTER_SUBJECTS
  dict. ADAPTER_SUBJECTS is retained as an alias to _SUBJECTS_BARE for any
  legacy importers; the dispatcher path is unchanged.
- Per-adapter subject patterns (region='us.id' default):
    nws        -> central.wx.alert.us.id.>     (region BEFORE wildcard)
    usgs_quake -> central.quake.event.>.us.id  (region AFTER wildcard)
    firms      -> central.fire.hotspot.>.us.id
    fires      -> central.fire.incident.id.>   (state token at fixed depth)
                  central.fire.perimeter.id.>
    traffic    -> central.traffic.>.id          (bare state, no us. prefix)
    roads511   -> central.traffic.>.id          (shared with traffic, sub-adapter routing)
    usgs       -> central.hydro.>.us.id
                  central.hydro.>.unknown      (workaround until v0.9.20.1)
    swpc       -> central.space.>               (planetary; region ignored)
- Empty/None region falls back to bare wildcards (pre-v0.9.20 behaviour).
- _subject_owned() pulls region from env.central.region and routes through
  _subjects_for; v0.5.3 sub-adapter routing (owned-sources set) still
  applies on shared subjects like central.traffic.>.id.
- start() logs the active region at connect-time for ops visibility.

Config (meshai/config.py):
- CentralConsumerConfig.region: str = "us.id". One region per consumer
  applies to every central-flipped adapter; per-adapter overrides can
  land in v0.6 when there is a real use case.

Frontend (dashboard-frontend/src/pages/Environment.tsx):
- Central Connection panel gets a Region text input next to URL/Durable.
- EnvConfig.central type extended with region: string.
- Static bundle rebuilt; index-DCFmSeOM.js -> index-B24tHcYj.js.

Tests:
- tests/test_central_region_routing.py (new, 9 cases): asserts the exact
  v0.9.20 subject string for each adapter at region='us.id', the SWPC
  global-stays-global rule, the USGS .unknown workaround, the empty-region
  backward-compat fallback for all 8 adapters, and integration through
  CentralConsumer._subject_owned() with the default region.
- tests/test_central_consumer.py + tests/test_central_sub_adapter_routing.py:
  the two tests that asserted bare-wildcard subjects now set
  env.central.region = "" explicitly to preserve their original concern
  (no region semantics — backward-compat path only).

Why swpc stays global: space weather is planetary -- a CME is detected on
the sun, the geomagnetic response is hemispheric. There is no Idaho-only
solar event; subscribing per-region would only drop events we want.

Why hydro has the .unknown workaround: Central v0.9.20 leaves gauges
whose USGS state can't be inferred on central.hydro.>.unknown. Until
v0.9.20.1 backfills the state tag we subscribe to both filters to
avoid silently losing those rows. Idaho downstream-filtering on
data['_enriched']['usgs_site']['state'] is future v0.6 work.

Orthogonal to v0.5.2 dispatcher guards (staleness / cooldown / dedup)
and v0.5.3 sub-adapter routing: the region filter operates at the NATS
subscription layer (server-side), upstream of everything else.

Verified: pytest 327 passed (318 prior + 9 new region-routing tests);
py_compile clean; frontend build clean. Safe-mode preserved -- no toggle
enabled, no master enabled, no central enabled.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-06-04 02:30:33 +00:00
matt+claude
ded2156024 feat(central): v0.5.3 -- roads511 + FIRMS central feeds with sub-adapter routing (recover stashed v0.5.1)
C.2 (v0.4) marked roads511 and FIRMS native-only in the dashboard despite the
Central event bus shipping both stream families. This recovers the v0.5.1
work paused before the v0.5.2 spam fix and lands it cleanly on top of v0.5.2.

Backend (meshai/central/consumer.py):
- ADAPTER_SUBJECTS: roads511 now subscribes to central.traffic.> (shared
  with the existing traffic adapter); firms widened from
  central.fire.hotspot.> to central.fire.>.
- CENTRAL_ADAPTER_TO_SOURCE: three new sub-adapter remaps so the inner
  envelope.adapter routes correctly inside a shared subject --
  tomtom_incidents -> traffic, state_511_atis -> roads511, firms -> firms.
- New _subject_owned() -> dict[subject_filter, set[meshai_source]]:
  builds the per-subject ownership set so a single central.traffic.>
  subscription can be owned by {traffic, roads511} simultaneously.
- subjects() now derives from _subject_owned().keys().
- _make_cb(owned) binds the owned set per-subscription; _on_message
  forwards it.
- _handle(subject, raw, owned=None) drops events whose remapped source
  isn't in the owned set (silent debug log). Enabling roads511 alone
  no longer accidentally consumes wzdx; enabling traffic alone no
  longer consumes state_511_atis.
- start() subscribes per (subject, owned) tuple; per-subject log line
  records the owned sources at startup.
- Removed roads511 from the "no Central mapping" warning loop now that
  it has one.

Frontend (dashboard-frontend/src/pages/Environment.tsx):
- roads511 META entry: hasCentral false -> true, nativeOnly true -> false
  (the FIRMS entry was already correct).
- Static bundle rebuilt via npm run build; old index-CfYlhn4e.js dropped,
  new index-DCFmSeOM.js + index-DjhQa8Mv.css land under static/assets;
  index.html updated to the new bundle hash.

Tests (tests/test_central_sub_adapter_routing.py, 8 new):
- roads511-only drops wzdx; emits state_511_atis as source=roads511.
- traffic+roads511 both central: wzdx -> traffic, state_511_atis -> roads511.
- firms-only drops wfigs_incidents; emits hotspots as source=firms.
- tomtom_incidents remaps to traffic.
- _subject_owned() shares central.traffic.> across {traffic, roads511}.

Orthogonal to v0.5.2's dispatcher guards: cooldown / dedup / staleness still
apply downstream of the consumer; the owned-sources filter operates one
layer up at message ingest. No changes to the dispatcher path.

Verified: pytest 318 passed (310 prior + 8 new routing tests); py_compile clean.
Safe-mode preserved -- no toggle enabled, no master enabled, no central enabled.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-06-04 02:16:54 +00:00
b90afc3a74 feat(notifications): v0.5.0 -- Master Toggles UX redesign + Central Connection GUI + grouped categories + region scoping
Per-family notification policy (PagerDuty/Grafana-style): each family gets a
severity threshold + region scope + a severity->channel routing matrix, so an
operator opts in per family rather than hand-writing rules.

SECTION 1 -- BACKEND
- config.py: new NotificationToggle dataclass (enabled, min_severity, regions,
  severity_channels{severity->[channel types]}, quiet_hours_override, + per-channel
  delivery config: broadcast_channel/node_ids/smtp_*/recipients/webhook_*).
  notifications.toggles is now a dict[family]->NotificationToggle with 8 family
  defaults (mesh_health, weather, fire, rf_propagation, roads, avalanche, seismic,
  tracking), all enabled=false (opt-in), min_severity=priority,
  severity_channels={priority:[mesh_broadcast], immediate:[mesh_broadcast, mesh_dm]},
  quiet_hours_override=true. (Old TogglesConfig.enabled was only read by
  build_pipeline via getattr -> degrades to ToggleFilter no-op, so the pipeline
  filter is unchanged; toggles now drive the Dispatcher instead.)
- region_scope:list added to NotificationRuleConfig; _matching_rules filters by
  event.region/regions ([] = all).
- Dispatcher: _dispatch_toggles runs IN PARALLEL to rule matching -- looks up
  get_toggle(event.category), checks enabled + region scope + severity threshold,
  then for each channel in severity_channels[event.severity] builds a synthetic
  rule (override_quiet set only for immediate when quiet_hours_override) and
  delivers. 'digest' channel is skipped in live dispatch (handled by accumulator).
- categories.py: get_toggle() prefix fallback maps the live phases-2.7-2.14
  categories (weather_warning, wildfire_incident, earthquake_event,
  traffic_congestion, geomagnetic/rf_*, stream_*, ...) to their family, fixing the
  v0.4 "category -> other" gap.
- config_loader.py: SECRET_FIELDS += notifications.toggles.*.smtp_password.
- _dataclass_to_dict now recurses dict-of-dataclasses, and the loader coerces the
  toggles dict -> NotificationToggle on both the full-load and section-PUT paths
  (so GUI save round-trips correctly).
- tests/test_notification_toggles.py (11): enabled/disabled, region filter
  (empty+populated+regions-list), severity threshold, per-severity channel routing,
  digest-skipped-live, quiet-hours-override immediate-only, category->family,
  rules+toggles both fire. Full suite: 294 passed (283 + 11).

SECTION 2 -- FRONTEND
- Notifications.tsx: MasterToggles component above the rules section -- 8 family
  cards (icon + enable toggle; collapsed summary 'OFF' or 'N regions, M channels at
  <sev>+'; expanded: severity threshold, severity x channel checkbox matrix,
  region list, quiet-hours-override toggle, per-channel config:
  broadcast_channel/DM node IDs/recipients/SMTP host+port/webhook URL).
- Environment.tsx: CentralConnectionPanel above the family tabs (url, durable,
  enabled) wired to environmental.central.
- npm run build clean (tsc strict); rebuilt static committed (index-CfYlhn4e.js).

SECTION 3 -- VERIFICATION
- py_compile + tsc strict clean; pytest 294 passed.
- Rebuilt prod: /notifications serves Master Toggles, /environment serves Central
  Connection (strings confirmed in the served bundle); 8 adapters, pipeline
  started, no tracebacks, healthy.
- GUI round-trip: enable weather toggle (min_severity=priority,
  regions=[Magic Valley], severity_channels.priority=[mesh_broadcast]) -> PUT
  {saved:true} -> notifications.yaml reflects it; env_feeds traffic.api_key stayed
  ${TOMTOM_API_KEY} (C.3.1 secret preservation holds). Restored to clean opt-in
  baseline.
- Synthetic NWS weather_warning/priority/Magic Valley -> routes through the weather
  toggle to mesh_broadcast; out-of-region and below-threshold events correctly
  dropped.

DEFERRED (noted for a follow-up, not blocking Matt's morning config): Section 2B
rules-editor polish -- grouped-by-family category checkboxes, region_scope
multi-select in the rule editor (backend field + filtering ARE in), tooltips, and
the fire-count Active/No-activity badge -- were not built tonight to keep the build
shippable and verified; the Advanced Rules section is otherwise unchanged and
still functional.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-28 07:00:10 +00:00
8eb0c6468c feat(dashboard): v0.4 C.2 family-tab restructure -- 7 families x per-adapter feed_source toggle
Restructures the environmental config UI into the 7-family taxonomy on the
/environment page (Matt-approved Option C: unified families = config +
live status per adapter). The editable adapter config moves out of the
Config page's "Environmental" tab (now deprecated) onto /environment, where
each adapter sub-tab shows its AdapterPanel (on/off + feed_source + settings)
together with its live status (feed health + active events).

Frontend (dashboard-frontend/src):
- pages/Environment.tsx rewritten: 7 family tabs (Weather, Fire, RF
  Propagation, Roads, Geohazards, Tracking, Mesh Health) -> per-family
  adapter sub-tab strip -> AdapterPanel.
  * AdapterPanel: header row = on/off Toggle + feed_source toggle
    (native|central). When OFF, feed_source + all settings grey out
    (disabled, not hidden).
  * Native-only adapters (ducting, avalanche, roads511 -- no Central stream
    per C.1's ADAPTER_SUBJECTS) show the feed_source toggle with 'central'
    disabled + a 'Central not available for this adapter' tooltip/label.
  * Missing-key adapters (firms, roads511) show an 'API key not configured
    -- contact admin' notice; toggles still operate.
  * Tracking = placeholder ('No adapters yet. ADS-B / AIS / satellite passes
    are planned for v0.5.'). Mesh Health = no env adapters; a disabled
    feed_source toggle with 'central' greyed for future migration.
  * All existing per-adapter settings preserved verbatim (NWS zones/user_agent/
    severity, ducting lat/lon, fires state, avalanche centers/season, USGS
    sites, traffic corridors+key, roads511 base_url/key/endpoints/bbox, FIRMS
    map_key/source/confidence/bbox). ALSO adds a usgs_quake panel
    (tick/min_magnitude/region/bbox) -- usgs_quake (2.14) was never exposed in
    the old GUI. feed_source field (C.1) now surfaced per adapter.
  * Per-adapter live status: reuses /api/env/status feed health + /api/env/active
    events (filtered by source; fires->nifc mapping). Refreshes every 30s.
- pages/Config.tsx: removed the now-duplicate 'Environmental' tab (SECTIONS
  entry + render case + EnvironmentalSection function + unused Thermometer
  import); exported the shared form primitives (Toggle, TextInput, NumberInput,
  SelectInput, ListInput, NumberListInput, US_STATES) for reuse by Environment.tsx.
- Reuses the existing restart_required banner pattern.
- Rebuilt static: meshai/dashboard/static/{index.html, assets/index-9OZ6ZqzI.js,
  index-B_J_Z7c8.css} (vite emptyOutDir replaced the old hashed bundle).

Rule 17 / no backend change: config is wired to the existing schema-driven
GET/PUT /api/config/environmental (the C.1 feed_source + central + usgs_quake
fields ride the generic dataclass coercion). No backend edited this phase.

Verification: (A) `npm run build` clean -- tsc strict + vite, only the
pre-existing >500kB single-chunk advisory (not introduced here). (B) static
committed; prod rebuilt picks it up. (C) GET / returns the new SPA shell
(index-9OZ6ZqzI.js); the bundle contains the new family strings (Geohazards,
RF Propagation, ADS-B, 'Central not available', 'API key not configured').
(D) GET /api/config/environmental returns all adapters with feed_source=native,
usgs_quake present, central{enabled:false} -- toggles bind to real data; all
native, nothing flipped. Rebuilt prod healthy.

*** BLOCKER FOUND (pre-existing, NOT introduced by C.2) -- flagged for C.2.1 ***
The save half of gate D fails: PUT /api/config/environmental returns
{"detail":"could not determine a constructor for the tag '!include' ..."}.
Root cause: the dashboard PUT handler (meshai/dashboard/api/config_routes.py)
calls meshai/config.py::save_config (monolithic; re-parses config.yaml with a
loader lacking the !include constructor) instead of the multi-file-aware
meshai/config_loader.py::save_section that exists for exactly the !include
layout. This breaks ALL GUI config saves in prod (every section, not just
environmental) and predates C.2 -- the old Config 'Environmental' tab had the
same broken save; C.1 did not touch save_config. The C.2 GET/render/toggle-bind
works; only persistence is blocked. Verified disk pristine (idempotent PUT
errored, wrote nothing; env_feeds.yaml md5 unchanged, restored from backup).
FIX (one line, but prod-wide blast radius -> wants its own phase + verification):
config_routes PUT should call config_loader.save_section(section, data, config_dir)
instead of config.save_config(...). Recommend C.2.1 backend fix before C.3.

C.3 (quake-to-central flip) should not proceed until C.2.1 unblocks GUI save,
since flipping feed_source from the GUI is the whole point.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-28 03:09:16 +00:00
49f2838048 refactor: simplify severity to 3 levels (routine/priority/immediate)
- Replace 6-level system (info/advisory/watch/warning/critical/emergency)
  with 3-level military precedence (routine/priority/immediate)
- Every adapter remapped: NWS, NIFC, FIRMS, USGS, SWPC, avalanche,
  traffic, 511, mesh alerts
- is_critical flag removed — severity covers it
- Quiet hours: suppress routine only, priority+immediate always deliver
- Dashboard: blue/amber/red for routine/priority/immediate
- Fix hex node ID parsing in Mesh DM channel (!23261b70 format)
2026-05-13 19:05:50 -06:00
f8874104ad feat(dashboard): alerts page + toast notifications + polish
- Full Alerts page with active alerts, history table, subscriptions
- Active alert cards with severity styling and acknowledge button
- Alert history table with type/severity filtering and pagination
- Subscription viewer showing mesh subscriptions
- ToastProvider for app-wide toast notifications
- Toast notifications triggered on WebSocket alert_fired messages
- Auto-dismiss toasts after 8 seconds, click to navigate
- Page titles on all pages (Dashboard/Mesh/Environment/Config/Alerts)
- Improved alert_routes.py with proper pending alert handling
- Added AlertHistoryItem, Subscription types to api.ts
- Added fetchAlertHistory, fetchSubscriptions functions

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-05-13 01:36:31 +00:00
3d74eb92b0 feat(env): add NASA FIRMS satellite fire hotspot detection
- Implement FIRMSAdapter polling NASA FIRMS area API for satellite hotspots
- Cross-reference hotspots against NIFC perimeters to identify new ignitions
- Add !hotspots command with --new flag for filtering new ignitions only
- Add FIRMSConfig dataclass with map_key, source, bbox, day_range options
- Add /api/env/hotspots endpoint for dashboard integration
- Add Satellite Hotspots section to Environment.tsx with NEW badges
- Add FIRMS configuration section to Config.tsx with source/confidence options
- Update config.example.yaml with FIRMS configuration template

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-05-12 23:06:55 +00:00
f8bf7e5057 feat(env): USGS stream gauges, TomTom traffic, 511 road conditions 2026-05-12 22:22:57 +00:00
2255ca5803 feat(env): NIFC fire perimeters + avalanche advisories
- WFIGS ArcGIS fire perimeter polling with proximity alerts
- Avalanche.org advisory polling (seasonal, SNFAC)
- !fire and !avy commands
- Distance-based severity for fires near mesh infrastructure
- Dashboard environment page integration
- Alert engine fires on fires within 50km of mesh area

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-05-12 15:22:07 -06:00
1158e30c0b Make environmental feeds band-agnostic; add Environment page
- Remove band_assessment and band_detail from SWPC adapter
- Remove all frequency-specific conclusions (906 MHz, 10m-20m, etc.)
- Store only raw indices: SFI, Kp, R/S/G scales, dM/dz gradients
- Let LLM interpret propagation data based on user's band of interest
- Add full Environment page with feed status, solar indices, and ducting data
- Update Dashboard RF Propagation card to show raw values only
- Update alert messages to be frequency-agnostic

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-05-12 14:59:54 -06:00
4331bcb7e1 feat(dashboard): React frontend scaffold with overview page
- Vite + React 18 + TypeScript + Tailwind CSS
- Dashboard overview with health gauge, pillar bars, alerts
- WebSocket hook for real-time updates
- Layout with sidebar navigation and live indicator
- Placeholder pages for Mesh, Environment, Config, Alerts
- Dark theme ops center aesthetic with JetBrains Mono

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-05-12 10:28:12 -06:00