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>
Gap 1 -- env_routes.lookup_usgs_site no longer creates a temporary
USGSStreamsAdapter to hit USGS.gov directly. When the env_store has no
native usgs adapter (because usgs.feed_source != native), the endpoint
returns HTTP 404 with a body that says "site lookup unavailable in
central-feed mode; values must be entered manually or sourced from
Central". This closes the AND-mode anti-pattern Central's v0.10.2
report flagged: meshai was in central-feed mode for usgs but the
lookup helper would still call USGS.gov directly the first time the
dashboard opened the Add-Gauge form.
Gap 2 -- config_routes.RESTART_REQUIRED_SECTIONS gains "environmental"
and the PUT handler now diffs the section before/after, returning
{saved, restart_required, changed_keys}. restart_required is true only
when there are actual changes AND the section is in the restart-required
set, so a no-op PUT to environmental never raises a false alarm.
Frontend wiring:
- New RestartBanner component (yellow top-of-main banner) listens to a
meshai:restart-required CustomEvent + cross-tab storage event,
persists across navigations via localStorage, shows changed_keys
preview + Restart-now button (POSTs /api/system/restart) + dismiss.
- Layout.tsx mounts <RestartBanner /> above {children} so it surfaces
on every page.
- Config.tsx saveSection() now calls notifyRestartRequired(changed_keys)
alongside its existing setRestartRequired(true) when the API flags
the section.
- GaugeSites.tsx probes /api/config/environmental at mount and shows a
"USGS lookup" button next to the site_id input. The button is
disabled with an explanatory tooltip when usgs.feed_source != native,
and gracefully renders the 404 detail when the API returns 404 in
central-feed mode -- enter-manually UX, no silent fallback.
Tests -- tests/test_or_arch_continuous.py (11 cases, all passing):
- USGS lookup 404 with no env_store / no native usgs adapter
- 502 on native-adapter exception
- 200 + payload on native-adapter happy path
- environmental in RESTART_REQUIRED_SECTIONS
- PUT environmental with changed feed_source -> restart_required:true
+ changed_keys list including foo.feed_source dotted path
- PUT bot (non-restart section) -> restart_required:false
- No-op PUT to bot / environmental -> restart_required:false, empty
changed_keys
- _diff_keys helper unit tests (nested dicts, list-element changes)
Why this matters: per the Spokane post-mortem and Central's v0.10.2
response, both sides need belt-and-suspenders against transient
AND-modes. meshai's static OR enforcement at env_store boot is the
runtime guard; this commit makes the GUI honor it continuously --
the lookup helper can't sneak past it any more, and the user is told
explicitly that an environmental config change does not take effect
until the container restarts.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Closes Section A.5 (gauge_sites) and A.12 (town_anchors) of the audit
doc by lifting both Python-dict curation lists into editable SQLite
tables. Operators can add/edit/disable rows from the dashboard without
a deploy; runtime reads go through cached accessors that invalidate
when the REST API mutates state.
Schema:
v8.sql adds gauge_sites(site_id PK, gauge_name, lat, lon, action_ft,
flood_minor_ft, flood_moderate_ft, flood_major_ft, enabled, updated_at).
v9.sql adds town_anchors(anchor_id AUTOINC PK, name UNIQUE, lat, lon,
state, enabled, updated_at).
SCHEMA_VERSION 7 -> 9.
Seed (meshai/persistence/curation.py):
_GAUGE_SITES_SEED carries the original 9 Idaho rows from
IDAHO_CURATED_SITES verbatim.
_TOWN_ANCHORS_SEED carries the 29 Idaho-and-neighbor towns from
_TOWN_COORDS verbatim.
seed_gauge_sites() / seed_town_anchors() INSERT OR IGNORE -- safe to
re-run; never overwrites user edits.
Handler integration:
- meshai/central/idaho_gauge_sites.py: IDAHO_CURATED_SITES dict deleted.
lookup_site() now calls meshai.persistence.curation.lookup_gauge_site()
which reads the table. THRESHOLD_RANK, normalize_site_id, and
compute_threshold_state remain in this module (CODE per Matt s rule).
- meshai/central/nwis_handler.py drops IDAHO_CURATED_SITES from its
import list; the table-backed lookup_site() is API-compatible.
- meshai/central_normalizer.py: _TOWN_COORDS dict deleted.
_compute_distance_bearing() now calls
meshai.persistence.curation.lookup_town_anchor() with the same
lowercased-name semantics it always used.
REST API (meshai/dashboard/api/curation_routes.py):
/api/gauge-sites GET list, GET one, POST add, PUT update, DELETE
/api/town-anchors GET list, GET one, POST add, PUT update, DELETE
Every mutation calls invalidate_curation_cache() so handler reads see
the new state on the next call -- no container restart.
Dashboard (dashboard-frontend/src/pages/):
- GaugeSites.tsx: table view with Add row / Edit row inline / Delete
confirm + per-row enabled toggle. 8 columns mirror the schema.
- TownAnchors.tsx: same pattern, 5 columns. Name is lowercased on
save to match the lookup key.
- Left-nav entries "Gauge Sites" (Droplets icon) and "Town Anchors"
(MapPin icon) added to Layout.tsx; routes added to App.tsx.
Tests (tests/test_curation.py, 18 cases):
- v8/v9 tables exist
- Seed lands every row from both dicts
- Seed idempotent; never overwrites user edits
- lookup_gauge_site hits/miss, disabled rows are invisible
- lookup_town_anchor case-insensitive
- REST API: GET list, GET one, GET 404, POST add, PUT update, DELETE,
POST missing-field 400; both gauge_sites + town_anchors
- Accessor reflects API mutations after invalidate_curation_cache()
tests/test_nwis_handler.py back-compat: IDAHO_CURATED_SITES dict alias
points at _GAUGE_SITES_SEED so the existing assertion suite still passes.
tests/test_adapter_config_foundation.py schema_meta v7 -> v9 bump.
Test count: 797 -> 819 (+18 curation cases + 4 maintenance updates).
Closes the audit-doc Section A keystone (the GUI editor). Together with
v0.6-3a foundation, v0.6-3a.1 trim, and v0.6-3b handler wiring, every
Rule-17 CONFIG knob from the audit is now editable in the dashboard
without a container restart.
API (meshai/dashboard/api/adapter_config_routes.py):
GET /api/adapter-config -- {adapter: [{key, value, default,
type, description}]}
GET /api/adapter-config/<adapter> -- one adapter list
GET /api/adapter-config/<adapter>/<key> -- single row
PUT /api/adapter-config/<adapter>/<key> body {value} -- typed validation
int: int or whole-number float; rejects bool, fractional float, str
float: int or float; rejects bool
str: str only
bool: bool only
json: any JSON-serializable value
Every PUT calls invalidate_cache() so the next handler accessor
read sees the new value -- no container restart needed.
POST /api/adapter-config/<adapter>/<key>/reset -- value_json = default_json,
cache invalidated
GET /api/adapter-meta -- {adapter: {display_name,
include_in_llm_context, description}}
PUT /api/adapter-meta/<adapter> partial-update body, fields:
include_in_llm_context: bool, display_name: non-empty str
Dashboard (dashboard-frontend/src/pages/AdapterConfig.tsx):
- Per-adapter cards. Header row shows display_name, the include_in_llm_context
toggle, and an expand chevron. Adapters with zero config keys (e.g. itd_511)
still render so users can toggle their LLM-context inclusion.
- Expanded body lists each key with a type-aware widget:
bool -> checkbox, commit-on-change
int/float -> number input, commit-on-blur (or Enter)
str -> text input, commit-on-blur
json -> textarea, commit-on-blur (JSON.parse with inline error)
Each row shows the key name, type tag, description, "edited" badge when
value != default, a per-key Reset button, and a save badge (spinner,
check, error tooltip, or a small amber dot for unsaved local changes).
- Auto-save semantics: every blur/change/reset triggers PUT immediately;
no explicit Save button needed. Reset is one-click per key.
Wiring:
- meshai/dashboard/server.py registers the new router with prefix /api.
- dashboard-frontend/src/App.tsx adds the /adapter-config route.
- dashboard-frontend/src/components/Layout.tsx adds the left-nav entry
(Sliders icon, label "Adapter Config", after Reference).
- Vite build produces a fresh meshai/dashboard/static bundle. The
Dockerfile copies meshai/ so the new bundle ships with the container
image at next rebuild.
Tests (tests/test_adapter_config_api.py, 30 cases):
- GET grouped, per-adapter, single key
- GET per-adapter returns [] for adapters with zero keys (itd_511)
- PUT updates value, GET shows new value, accessor returns new value
(proves cache invalidation propagates to the in-process accessor)
- PUT type validation per (int, float, str, bool, json) incl. edge cases:
int rejects str + fractional float + bool but accepts whole-number float;
float accepts int + float, rejects bool; bool rejects int; str rejects
other types; json accepts list / dict / None
- PUT 404 on unknown key, 400 on missing value field
- POST reset restores default + invalidates cache
- GET /api/adapter-meta: include_in_llm_context defaults match registry
(central / geocoder false, rest true)
- PUT meta partial update: only provided fields change
- PUT meta rejects non-bool include_in_llm_context, empty display_name,
unknown adapter
Test count: 731 -> 761 (+30 API cases, 0 regressions).
Refs audit doc v0.6-phase1-audit.md Section A keystone + finding #4.
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.
- Add GET /api/channels endpoint for live radio channel data
- Create ChannelPicker component (single/multi-select from live channels)
- Create NodePicker component (searchable multi-select from mesh nodes)
- Replace manual inputs in Config with data-driven pickers
- Update Notifications to use pickers for mesh broadcast/DM
- Resolve node names in Alerts subscriptions display
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Create standalone Notifications.tsx page with full notification config UI
- Add /notifications route in App.tsx
- Add Notifications nav item in Layout.tsx sidebar (below Alerts, BellRing icon)
- Remove notifications section from Config.tsx (keep settings sections only)
- Channels, rules, quiet hours, and dedup all configurable on dedicated page
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- 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>
- Added blur config for dimmed state (opacity 0.15 for nodes, 0.04 for edges)
- Added scale effect on node hover (1.1x)
- Explicit edgeSymbol: none to disable edge markers
- Only nodes are interactive: hover, click, tooltip
- Edges have no hover effect, no tooltip, no click handler
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Replaced React state-driven animation with ECharts graph rendering.
Previous approach re-rendered 263+ SVG nodes via setState in rAF loop,
causing browser lockup. New approach uses ECharts which handles force
layout and rendering natively at scale.
Changes:
- Switch from D3 force sim + React state to ECharts graph
- Remove particle animation (was fake random noise, not real packets)
- Filter out nodes with zero edges by default
- Add filter controls: Connected, Infra, All
- ECharts handles zoom/pan/drag natively (roam: true)
- Node selection dims unrelated nodes
- Force config matches Meshview: repulsion 200, edgeLength [80,120]
- Animation disabled for performance (animation: false)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Dynamic force parameters based on node/edge count and density
- Per-node degree-weighted link strength for balanced layouts
- Position clamping keeps nodes within viewport
- d3-zoom for pan/zoom (wheel, drag, double-click reset)
- Zoom control buttons (+, -, reset) in corner
- Drag handlers account for zoom transform
- Legends stay fixed outside transform group
- Scales gracefully from 63 to 200+ nodes
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- 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>