Commit graph

190 commits

Author SHA1 Message Date
5c31dbdf4a fix(infra): point meshai container DNS at LXC working resolver
The meshai container could not resolve external HTTP hostnames (NWS
api.weather.gov, SWPC services.swpc.noaa.gov, and the meshview mesh
source), failing every poll with "[Errno -3] Temporary failure in name
resolution". Docker's embedded resolver (127.0.0.11) forwards to the
daemon default upstreams 1.1.1.1/8.8.8.8, which are unreachable from
this container's NAT egress (the same egress filter that blocks Docker
Hub). The radio link was unaffected because it is an IP, not a hostname.

Fix: pin the meshai service to dns: [100.100.100.100], the LXC host's
own working resolver (Tailscale MagicDNS). The LXC's /etc/resolv.conf
uses only 100.100.100.100 and resolves the public feeds fine, and it
forwards public queries upstream. A preflight `docker run --dns=
100.100.100.100 ... getent hosts api.weather.gov` resolved successfully
from the docker bridge, confirming the container can reach MagicDNS.

Chosen over network_mode: host (more invasive, needs port-binding
review) and a host-side daemon.json dns key (affects all containers,
lives outside git). This directive is in-repo, git-tracked, and survives
daemon reloads.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-27 17:15:29 +00:00
074e020463 feat(notifications): Phase 2.6.5 wire EventBus into main.py runtime path
Closes the dark store->bus path. The to_event() methods added in Phase
2.6 for NWS and FIRMS were exercised only by unit tests because main.py
never built the pipeline or passed an EventBus to EnvironmentalStore.

Insertion points (matching existing init/lifecycle conventions):
- _init_components(): inside the notifications.enabled block, after the
  NotificationRouter init, build the v0.3 pipeline via build_pipeline()
  and stash it on self.event_bus; then construct EnvironmentalStore with
  event_bus=self.event_bus so newly-seen adapter events emit to the bus.
- start(): after _write_pid(), await start_pipeline() to launch the
  digest scheduler now that the event loop is running; the scheduler is
  stored on self._pipeline_scheduler.
- stop(): await stop_pipeline() during teardown.
- env/store._emit_event(): emission log promoted DEBUG->INFO for runtime
  traceability of events crossing the bus.

When notifications are disabled, self.event_bus stays None and the store
receives None (emission no-ops), preserving prior behavior.

Tests: 132 passing, no regressions.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-27 15:39:40 +00:00
9c5a106c9f feat(env): Phase 2.6 FIRMS adapter emits Events to pipeline bus
Second adapter wired to the new pipeline (after NWS). Reuses the
store-side emission logic added in the NWS commit.

- FIRMSAdapter.to_event() maps stored dict to pipeline Event.
- Category decision: new_ignition vs wildfire_proximity based on
  properties.new_ignition (computed by FIRMS during ingest from
  proximity to known fires).
- Severity passes through (FIRMS already pre-maps to our 3-level
  system during _parse_csv).
- group_key and inhibit_keys use a spatial grid key
  (firms:LAT:LON rounded to 0.01 degrees, ~1km) so repeated
  satellite detections of the same hotspot are coalesced and
  lower-severity re-detections are inhibited.
- Summary text enriched with FRP, confidence, and distance from
  the nearest region anchor when present.
- 13 tests covering category decision, severity pass-through,
  spatial grouping, and defensive handling of incomplete dicts.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-05-15 05:23:00 +00:00
95dc938c2a feat(notifications): Phase 2.6 NWS adapter pipeline integration
Wires the NWS adapter to the new notification pipeline via EventBus:

- Added fine-grained weather categories: weather_watch, weather_advisory,
  weather_statement (all routine severity) alongside existing weather_warning
- NWSAlertsAdapter._derive_category() maps NWS event type suffix to category:
  "Warning" -> weather_warning, "Watch" -> weather_watch, etc.
- NWSAlertsAdapter.to_event() converts internal event dict to pipeline Event
  with proper group_key (event_id) and inhibit_keys (Warning suppresses Watch)
- EnvironmentalStore accepts optional event_bus parameter
- EnvironmentalStore._ingest() emits new events to bus via _emit_event()
- 22 new tests in test_adapter_nws.py covering category derivation,
  severity mapping, and Event field population

All 119 tests pass.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-05-15 04:47:31 +00:00
b2bb7f7a95 feat(notifications): Phase 2.5b per-channel-type renderers
Adds dedicated renderer classes per channel type:

- MeshRenderer produces 1+ chunks <=200 chars with (k/N) counters
  when the payload overflows. Reuses the toggle-label vocabulary
  from the digest. Mesh channels skip re-chunking when the payload
  already carries chunk_index metadata (digest path).
- EmailRenderer produces {subject, body} with structured context
  lines. Plain text only; HTML body is a future polish.
- WebhookRenderer produces a JSON-serializable dict with stable
  schema_version 1.0. Optional fields omitted (not nulled) for
  compactness. Designed for reuse by Phase 2.6.5's MQTT event
  publisher.
- All four channel implementations (MeshBroadcast, MeshDM, Email,
  Webhook) now call their renderer in deliver() before transport.
- New renderer tests cover each renderer in isolation; new channel
  integration tests confirm channels actually call their renderer.

Renderers are pure functions of the payload - no network, no
state, fully testable without mocking I/O. The future MQTT
publisher will instantiate WebhookRenderer directly.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-05-15 04:25:44 +00:00
c9d9a9925c feat(notifications): Phase 2.5a channel interface unification
- Switch channels.py from dict-based to dataclass-based interfaces
- Add NotificationPayload dataclass and make_payload_from_event helper
- Update channel.deliver() to be async with (payload, rule) signature
- Add connector parameter to Dispatcher, DigestScheduler, and pipeline builders
- Update pipeline tee to use asyncio.create_task for async dispatch
- Add create_channel_from_dict for legacy router.py compatibility
- Update tests for new async interfaces

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-05-15 03:45:27 +00:00
a4cb29002d fix(notifications): inject llm_backend into build_pipeline
build_pipeline previously constructed its own LLMBackend from
config.llm, which:
  - duplicated main.py's already-running backend instance
  - failed to inherit env-loaded LLM_API_KEY when called from
    short-lived scripts (eyeball checks, tests), forcing fallback
  - prevented pipeline components from sharing the live backend

build_pipeline and build_pipeline_components now require an
llm_backend parameter. main.py passes the same instance it
constructed for its primary responder. Tests pass mocks. The
digest accumulator now uses the live, authenticated backend.

Added test_build_pipeline_uses_provided_backend to lock in the
injection contract.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-05-15 03:08:31 +00:00
9674e94efb Phase 2.4: LLM-summarized digest with master toggle filter
- Remove severity-based fork; tee pattern sends all events to both dispatcher and accumulator
- Add ToggleFilter before tee; drops events for disabled toggles
- Rework DigestAccumulator: event log instead of active/resolved tracking
- render_digest now async, calls LLM once per toggle with severity-ordered events
- Fallback to count-based summary when LLM unavailable
- Add TogglesConfig to config.py for master toggle settings
- Update scheduler to await async render_digest
- 75 tests passing

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-05-15 02:37:12 +00:00
d6bc6b2b89 build: normalize all line endings to LF
One-time renormalization pass under the .gitattributes added in the
previous commit. Every tracked text file now uses LF. No semantic
changes — verified via git diff --cached --ignore-all-space showing
zero real differences. Future diffs will only show real content
changes.

This commit will appear huge in git log --stat but represents zero
behavior change. Use git log --follow --ignore-all-space or
git blame -w when archaeologically tracing through this commit.
2026-05-14 22:43:06 +00:00
211c642b60 build: add .gitattributes to enforce LF line endings
Forces all text files to use LF on commit regardless of the editor or
OS that wrote them. Prevents the line-ending churn that has been
inflating diffs (e.g. 1510 lines of churn on config.py in commit
493b43f for what was really a 12-line change). The next commit will
normalize all existing files; from there forward, diffs only show
real semantic changes.
2026-05-14 22:42:14 +00:00
493b43f7cf feat(notifications): Phase 2.3b digest scheduler
Adds DigestScheduler class that fires digest at configured time (default 07:00)
and routes to rules with trigger_type=schedule and schedule_match=digest.

- DigestScheduler: asyncio task with start/stop lifecycle
- Config: DigestConfig dataclass with schedule and include fields
- Config: schedule_match field on NotificationRuleConfig
- Pipeline: start_pipeline/stop_pipeline async lifecycle functions
- Mesh channels get per-chunk delivery, email/webhook get full text
- 26 new tests covering schedule computation, fire behavior, lifecycle

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-05-14 22:32:51 +00:00
8326fc56b2 refactor(notifications): mesh chunk list and include_toggles 2026-05-14 21:39:35 +00:00
57e2f516c5 refactor(notifications): per-toggle digest lines, exclude rf_propagation, explicit empty digest 2026-05-14 20:48:40 +00:00
96de22c6c0 feat(notifications): Phase 2.3a digest accumulator and renderer
Adds DigestAccumulator tracking ACTIVE NOW and SINCE LAST DIGEST
state per toggle. Replaces StubDigestQueue in build_pipeline; the
stub class is kept for Phase 2.1 backward-compat tests.

- enqueue(): adds new events, updates in place by id, detects
  resolutions (expires past, or title contains cleared/reopened/
  ended/resolved/back online/recovered/lifted)
- tick(now): rolls expired actives into since_last
- render_digest(now): produces a Digest with mesh_compact (<=200
  chars) and full multi-line forms; clears since_last after
- Toggle ordering and labels match the v0.3 design
- Phase 2.3b will add real scheduling on top of this
2026-05-14 19:21:40 +00:00
e67e2cd6a0 feat(notifications): Phase 2.2 inhibitor and grouper
Adds inline pipeline stages between the bus and the severity router:

- Inhibitor: suppresses lower-or-equal severity events when a key
  in event.inhibit_keys is already active. TTL configurable, default
  30 minutes.
- Grouper: coalesces events sharing group_key within a time window
  (default 60s). Most recent event wins. tick() and flush_all()
  drive emission; no background timers in Phase 2.2.
- build_pipeline now wires: bus -> inhibitor -> grouper -> severity_router

Phase 2.1 dispatcher tests continue to pass unchanged.
2026-05-14 18:53:03 +00:00
31fe4d5978 test(notifications): six test cases for Phase 2.1 pipeline 2026-05-14 18:21:24 +00:00
866c55a91c fix(notifications): align Phase 2.1 dispatcher with spec
The initial 2.1 dispatcher was a logging stub with manual backend
registration. The spec required integration with the existing
NotificationRuleConfig schema and channels.py create_channel factory.

- Dispatcher takes (config, channel_factory)
- _matching_rules iterates config.notifications.rules with severity ranking
- dispatch() builds alert dict and calls channel.deliver()
- build_pipeline(config) returns EventBus per spec
- build_pipeline_components(config) added for test introspection
2026-05-14 18:06:08 +00:00
Ubuntu
4e4a837c5e feat(notifications): add Phase 1.3 + 2.1 pipeline skeleton
Phase 1.3:
- events.py: Event dataclass with ID generation and serialization
- region_tagger.py: Coordinate/NWS zone region tagging
- categories.py: Toggle field mapping for all 31 alert categories

Phase 2.1 Pipeline Skeleton:
- pipeline/bus.py: EventBus with subscribe/emit pattern
- pipeline/severity_router.py: Routes immediate->dispatch, routine->digest
- pipeline/dispatcher.py: Delivers immediate events to configured channels
- pipeline/__init__.py: build_pipeline() factory and exports

All components tested and verified in container.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-05-14 17:21:45 +00:00
0703d00d94 feat(notifications): map alert categories to v0.3 toggles
Adds toggle field to each ALERT_CATEGORIES entry:
- mesh_health: 18 categories (infra, power, utilization, coverage, health)
- weather: 3 categories (NWS warnings, stream flooding)
- fire: 3 categories (NIFC, FIRMS hotspots)
- rf_propagation: 3 categories (solar, geomag, ducting)
- roads: 2 categories (closures, congestion)
- avalanche: 2 categories (high danger, considerable)

Also adds helper functions:
- categories_for_toggle(toggle) -> list of category IDs
- get_toggle(category_name) -> toggle name or None

Note: seismic and tracking toggles defined but have no categories yet
(reserved for Phase 3 and Phase 7 respectively).

All toggle assignments are unambiguous - no categories defaulted to
mesh_health due to ambiguity.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-05-14 16:29:50 +00:00
e6897b3f33 feat(notifications): add region tagger with coordinate and NWS zone matching
Adds meshai/notifications/region_tagger.py with:
- haversine_distance() for great-circle distance calculation
- tag_by_coordinates() maps lat/lon to nearest region within radius
- tag_by_nws_zone() maps NWS zone codes to matching regions

Also adds nws_zones field to RegionAnchor in config.py to support
zone-based matching. Default is empty list for backward compatibility.

This is scaffolding for Phase 2 - not yet wired into any adapters.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-05-14 16:26:53 +00:00
dc52187c93 feat(notifications): add Event dataclass for v0.3 pipeline
Adds meshai/notifications/events.py with:
- Event dataclass with all fields for unified pipeline shape
- Stable ID generation via sha1 hash for deduplication
- make_event() factory with auto-timestamp and severity validation
- to_dict/from_dict for serialization round-trip

This is scaffolding for Phase 2 - not yet wired into any adapters.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-05-14 16:23:57 +00:00
5274933fa0 fix(migration): deep-equality verification gate for full Config
- Added deep_compare function for recursive dict comparison
- Replaced shallow key-list check with full Config dataclass comparison
- Uses dataclasses.asdict for consistent dict representation
- Reports full path of mismatches (e.g. connection.tcp_host)

The previous gate only checked inline sections and missed the
include-related bugs that caused the restart loop.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-05-14 16:14:27 +00:00
67ab2689fe fix(config): correct meshtastic include nesting
- Changed orchestrator to use meshtastic: include meshtastic.yaml
- Added hoisting logic to extract connection/commands from wrapper
- Fixes restart loop caused by connection.type defaulting to serial

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-05-14 16:14:18 +00:00
965a844b0d feat(config): split monolithic config + extract secrets
- Update .gitignore for v0.3 multi-file layout
- Add config/.env.example template for secrets
- Add config/local.yaml.example for operator values
- Wire main.py to use new config_loader
- Support both legacy and new layouts

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-05-14 15:14:12 +00:00
2c11432bd8 feat(config): add migration script for v0.2 to v0.3 layout
- Backup original config before migration
- Split monolithic config into domain files
- Extract operator-identifying values to local.yaml
- Extract secrets to /data/secrets/.env
- Create orchestrator with !include directives
- Post-migration verification
- Safe to run multiple times (idempotent checks)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-05-14 15:14:05 +00:00
9e3f940a1b feat(config): add multi-file config loader with !include support
- Add config_loader.py with !include directive support
- Environment variable interpolation with default syntax
- local.yaml merging for operator-identifying values
- Secret loading from /data/secrets/.env
- save_section() for dashboard write-back
- Cycle detection for include directives
- Graceful degradation when files missing

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-05-14 15:13:56 +00:00
344ca0677d fix(notifications): complete severity cleanup to 3-level system
- Replace 11 info fallbacks with routine in router.py + channels.py
- Replace 2 warning min_severity defaults with priority
- Update config.example.yaml rules to use routine/priority/immediate
- Annotate config.example.yaml notifications section as transitional pending v0.3 8-toggle rewrite Phase 1.2

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-05-14 07:00:58 +00:00
95ec7d5351 fix: notification system improvements and threshold corrections
- Fix leftover severity references (info→routine in filter dropdown)
- Fix node_id int handling in connector and channels (handle both int and string)
- Add LLM-generated reports for notifications (replace raw data dumps)
- Fix health.score.composite attribute path for RF reports
- Add deterministic HF band conditions from SFI/Kp values
- Remove max_tokens from LLM calls (character limits at delivery)
- Weather feed improvements: show event_type + area, local events first
- Fix is_online to use configured offline_threshold_hours in data store
- Update stale defaults: offline 24→2h, battery_warning 20→30%
- Add TODO comments for packet_threshold scale bug

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-05-14 06:03:51 +00:00
7a4bd4f38f fix(mesh): use configured offline threshold in data store
- Add offline_threshold_hours parameter to MeshDataStore.__init__
- Compute is_online in _normalize_node using configured threshold
- Pass config.mesh_intelligence.offline_threshold_hours from main.py
- Removes reliance on health engine for initial is_online computation

Verification:
- Unit test confirms 2h threshold marks 3h-old node offline
- Unit test confirms 4h threshold marks same node online
- Container starts healthy with no config errors
- Health engine reports 16/16 infra online
2026-05-13 23:54:20 -06:00
21d6520ffd fix(dashboard): weather feed shows location + hazard, prioritizes local
- Event feed shows event_type + area_desc instead of timestamp headline
- First sentence of description shown as hazard summary
- Local events (matching NWS zones) pinned to top with highlight
- Nearby events grouped below, slightly dimmed
- Dedup by event_id
2026-05-13 20:33:48 -06:00
839bf322d9 fix(notifications): health attribute path + deterministic band conditions
- Fix MeshHealth.score.composite path (was accessing wrong object)
- Add deterministic band condition calculator from SFI/Kp/time
- RF reports use structured band format, not LLM
- Fix LLM prompts for health/weather reports (max_tokens, format)
- Graceful handling when data sources not configured
2026-05-13 20:11:16 -06:00
829ad562e4 feat(notifications): LLM-generated reports replace raw data dumps
- Status/report messages use LLM to generate operator-readable summaries
- RF reports interpret SFI/Kp into which bands are open
- Mesh reports highlight problems, not just numbers
- Remove meaningless [STATUS] prefix
- Alerts stay templated (no LLM, no latency)
- Reports respect 180-char limit for mesh delivery
2026-05-13 19:42:23 -06:00
32f6a238f8 fix: node_id int handling in connector + rule stats data path
- connector.send_message accepts int or string destination
- channels.py converts node_id to str before string operations
- Rule stats write to /data/ (Docker volume) not /opt/meshai/data/
2026-05-13 19:27:04 -06:00
2f0cf520fa fix: leftover old severity references (info→routine, filter dropdown) 2026-05-13 19:10:18 -06: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
5b78e38d2e Merge origin/feature/mesh-intelligence into feature/mesh-intelligence
Merged remote changes with local notification verification system:
- Kept local: channels.py, router.py, notification_routes.py, Notifications.tsx
  (contains the new end-to-end verification system)
- Accepted remote: Config, Environment, Reference pages, new commands,
  categories, summarizer, and other supporting files

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-05-13 18:41:36 -06:00
e35c0f5553 feat(notifications): end-to-end verification system
- Channel connectivity test: SMTP, webhook, mesh with real errors
- Rule test shows live data from feeds, not canned examples
- Near-miss detection: shows events filtered by threshold
- Three send actions: current conditions, example alert, live alert
- Rule status indicators: last fired, data source health
- All errors show actual error messages
- Disabled feed detection with clear warnings

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-05-13 18:40:18 -06:00
72a7a90f4d fix(notifications): test shows live data, not just canned examples
- Test always shows current data for the rule's feed categories
- RF rules show live SFI/Kp/R/S/G and ducting conditions
- Weather rules show active NWS alert count and headlines
- Fire rules show active fire/hotspot count
- Stream rules show current gauge readings
- Mesh rules show current health score and infra status
- Send Current Conditions delivers live snapshot through channel
- Send Test Alert delivers example through channel
- Send Live Alert available when real conditions match

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-05-13 18:08:09 -06:00
0ad37e55d9 fix(notifications): test button sends real data preview, not generic string
- Tests check current conditions against rule categories/severity
- Shows actual alert messages that would fire right now
- Falls back to example messages from category registry if no matches
- Preview mode shows without sending, Send Test delivers with [TEST] prefix
- Mesh delivery applies real summarization so preview matches actual output
- Added test dialog UI showing conditions matched and preview messages

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-05-13 23:32:22 +00:00
c6b4a64163 fix(health): use real telemetry, fix hardcoded thresholds
- Utilization pillar reads firmware channel_utilization (max of infra
  nodes) instead of estimating from packet counts × 200ms
- is_online uses configured threshold, not hardcoded 24 hours
- Updated defaults: offline 2h (was 24h), battery warning 30% (was 20%)
- Utilization thresholds: 20/25/35/45% matching real Meshtastic behavior
- Behavior pillar threshold aligned with notification config (7200/day)
- has_solar marked as dead code pending Solar Quality Engine

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-05-13 23:06:31 +00:00
57a19aeec6 fix(health): use real channel utilization from node telemetry
- Utilization pillar now reads firmware-reported channel_utilization
  instead of estimating from packet counts with hardcoded 200ms/pkt
- Uses highest infra node value (busiest node = bottleneck)
- Falls back to packet count estimate only when telemetry unavailable
- Updated thresholds: 20/25/35/45% matching real Meshtastic behavior
- Per-region utilization from region nodes, not mesh-wide
- API response includes util_method, util_max_percent, util_node_count

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-05-13 22:49:41 +00:00
7de02fb924 fix(health): adjust utilization thresholds to match real-world behavior
Updated channel utilization scoring thresholds:
- UTIL_HEALTHY: 15% -> 20% (channel is clear)
- UTIL_CAUTION: 20% -> 25% (slight degradation)
- UTIL_WARNING: 25% -> 35% (severe degradation)
- UTIL_UNHEALTHY: 35% -> 45% (mesh struggling)

Previous thresholds were overly conservative. New values better
reflect actual Meshtastic firmware behavior and when operators
should take action.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-05-13 22:35:37 +00:00
4ed154770d docs(dashboard): add detailed health pillar calculation explanations
Reference page now explains HOW each health pillar is calculated:
- Infrastructure: router online ratio
- Utilization: airtime estimation from packet counts
- Coverage: gateway redundancy with single-gw penalties
- Behavior: flagged node thresholds
- Power: battery warning ratio

Includes actual formulas and special cases.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-05-13 22:33:20 +00:00
abef593146 fix(dashboard): correct corrupted em-dash in Config page title
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-05-13 22:09:22 +00:00
23d7b21e8c feat(dashboard): reference library + notification rule templates
- Built-in Reference page with plain-English documentation
- 13 topics: stream gauges, wildfire, FIRMS, weather, solar,
  ducting, avalanche, traffic, 511, mesh health, notifications,
  commands, API
- Searchable topic sidebar with anchor navigation
- Notification rule templates: 6 presets for quick setup
  - Mesh Health Monitoring
  - Weather & Fire Alerts
  - RF Conditions
  - Road & Traffic
  - Everything Critical
  - Morning Briefing
- All tables styled with dark theme and color indicators

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-05-13 22:00:25 +00:00
23151f63ba fix(dashboard): info popover toggle and click-outside dismiss
- Replace fixed overlay with useRef-based click-outside detection
- Add X close button in top-right corner of popover
- Click ? to toggle (open if closed, close if open)
- Click anywhere outside popover to dismiss
- Remove fixed inset-0 overlay that was blocking page interaction

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-05-13 16:04:36 +00:00
9369bd684f feat(config): add comprehensive field documentation with info buttons
- Add helper text and info (?) buttons to every field in Config.tsx
- Add section descriptions at the top of each config section
- Battery thresholds now show voltage equivalents (e.g., "30% ≈ 3.60V")
- NWS severity dropdown shows descriptions per option
- Alert rules grouped by category with full explanations
- Add InfoButton popover component for detailed field documentation
- Add info buttons to Environment.tsx RF propagation panels
- VOLTAGE_MAP and getVoltageApprox helper for Li-ion voltage lookup

Researched defaults and descriptions include:
- Li-ion voltage curve (4.20V=100%, 3.60V=30%, 3.50V=15%, 3.40V=7%)
- LoRa channel utilization (firmware throttles at 25%, issues at 50%)
- Packet flood detection (normal 1-5/min, suspicious >10/min)
- NWS severity levels with actionable descriptions
- Tropospheric ducting M-units/km refractivity gradients
- NOAA Space Weather R/S/G scales

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-05-13 09:55:03 -06:00
64faf33e3b feat: researched defaults + USGS auto-lookup + category documentation
- Battery thresholds: 30%/15%/5% with voltage equivalents (3.60V/3.50V/3.40V)
- Channel utilization threshold: 40% (firmware throttles GPS at 25%)
- Packet flood threshold: 10 packets/min per node (was 500/day)
- Mesh health threshold: 65 (was 70)
- USGS adapter with NWS NWPS flood stage auto-lookup
- API endpoint: GET /api/env/usgs/lookup/{site_id}
- Alert categories with detailed descriptions and example messages
- Packet flood vs stream flood terminology fully disambiguated

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-05-13 15:14:16 +00:00
7286c9ab44 feat(dashboard): RF propagation visualizations + live event feed
- SFI/Kp as prominent color-coded values with trend chart
- R/S/G scales as colored severity badges
- Tropospheric ducting condition with refractivity profile
- Environmental feeds replaced with scrolling live event timeline
- Unified activity log across all 9 feed adapters
- Source icons, severity badges, chronological order
- Real-time updates via WebSocket
- SWPC adapter stores Kp/SFI history for charting
- No wasted card space

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-05-13 14:47:15 +00:00
d90b787c12 refactor(notifications): complete UX redesign
- Self-contained rules replace abstract channels
- Inline delivery config (broadcast/DM/email/webhook or none)
- quiet_hours_enabled master toggle separate from start/end times
- delivery_type="" valid: rule matches but does not deliver
- Severity dropdown with plain-English descriptions
- Example messages per alert category
- Default baseline rules: Emergency Broadcast, Infrastructure Down, Fire Alert, Severe Weather
- Condition vs Schedule trigger types
- Test and preview buttons per rule
- stream_flood_warning renamed from flood_warning (distinct from packet_flood)
- Categories display with descriptions

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-05-13 14:25:57 +00:00