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>
This commit is contained in:
malice 2026-07-03 14:57:24 -06:00 committed by GitHub
commit 0460462485
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
38 changed files with 2502 additions and 2342 deletions

View file

@ -0,0 +1,50 @@
"""Config round-trip tests for the MeshCore passive-context block.
Verifies that ``meshcore_context`` survives save_config -> load_config, and
that a YAML lacking the section yields the dataclass defaults (proving the
generic nested-dataclass loader branch handles it with no special-casing).
"""
import yaml
from meshai.config import (
Config,
MeshCoreContextConfig,
load_config,
save_config,
)
def test_meshcore_context_round_trip(tmp_path):
cfg = Config()
cfg.meshcore_context = MeshCoreContextConfig(
enable_passive_context=False,
observe_channels=["#aida", "#general"],
ignore_contacts=["a1b2", "SpamNode"],
respond_to_dms=False,
)
path = tmp_path / "config.yaml"
save_config(cfg, path)
loaded = load_config(path)
mc = loaded.meshcore_context
assert isinstance(mc, MeshCoreContextConfig)
assert mc.enable_passive_context is False
assert mc.observe_channels == ["#aida", "#general"]
assert mc.ignore_contacts == ["a1b2", "SpamNode"]
assert mc.respond_to_dms is False
def test_meshcore_context_defaults_when_absent(tmp_path):
# A minimal YAML with no meshcore_context section at all.
path = tmp_path / "config.yaml"
path.write_text(yaml.safe_dump({"timezone": "America/Boise"}))
loaded = load_config(path)
mc = loaded.meshcore_context
assert isinstance(mc, MeshCoreContextConfig)
assert mc.enable_passive_context is True
assert mc.observe_channels == []
assert mc.ignore_contacts == []
assert mc.respond_to_dms is True

View file

@ -0,0 +1,85 @@
"""Unit tests for the MeshCore passive-context / bot-behavior filter.
Covers the pure module-level helper ``mc_context_allows`` no hardware,
no meshcore lib, no event loop. MeshMessage and MeshCoreContextConfig are
constructed directly.
"""
from meshai.config import MeshCoreContextConfig
from meshai.connector import MeshMessage
from meshai.transport.meshcore_transport import mc_context_allows
def _dm(sender_id="a1b2", sender_name="Alice", text="hi"):
return MeshMessage(
sender_id=sender_id,
sender_name=sender_name,
text=text,
channel=0,
is_dm=True,
transport="meshcore",
)
def _chan(channel=1, text="hello", sender_id=None, sender_name=None):
marker = f"chan:{channel}"
return MeshMessage(
sender_id=sender_id or marker,
sender_name=sender_name or marker,
text=text,
channel=channel,
is_dm=False,
transport="meshcore",
)
def test_cfg_none_always_passes():
assert mc_context_allows(None, _dm(), {}) is True
assert mc_context_allows(None, _chan(), {}) is True
def test_defaults_pass_through():
cfg = MeshCoreContextConfig() # empty lists, passive on, respond_to_dms on
idx_to_name = {1: "#general"}
assert mc_context_allows(cfg, _chan(channel=1), idx_to_name) is True
assert mc_context_allows(cfg, _dm(), idx_to_name) is True
def test_observe_channels_filters_by_name():
cfg = MeshCoreContextConfig(observe_channels=["#aida"])
idx_to_name = {1: "#general", 2: "#aida"}
# idx 1 -> #general -> not in observe list -> DROPPED
assert mc_context_allows(cfg, _chan(channel=1), idx_to_name) is False
# idx 2 -> #aida -> in observe list -> PASSES
assert mc_context_allows(cfg, _chan(channel=2), idx_to_name) is True
# idx 3 -> no name mapping -> DROPPED
assert mc_context_allows(cfg, _chan(channel=3), idx_to_name) is False
def test_ignore_contacts_matches_id_or_name():
cfg = MeshCoreContextConfig(ignore_contacts=["a1b2"])
# matched by sender_id
assert mc_context_allows(cfg, _dm(sender_id="a1b2", sender_name="Alice"), {}) is False
# matched by sender_name
assert mc_context_allows(cfg, _dm(sender_id="ffff", sender_name="a1b2"), {}) is False
# unrelated DM passes
assert mc_context_allows(cfg, _dm(sender_id="c3d4", sender_name="Bob"), {}) is True
def test_respond_to_dms_false_drops_dms_only():
cfg = MeshCoreContextConfig(respond_to_dms=False)
idx_to_name = {1: "#general"}
# any DM dropped
assert mc_context_allows(cfg, _dm(), idx_to_name) is False
assert mc_context_allows(cfg, _dm(sender_id="zzzz", sender_name="Zed"), idx_to_name) is False
# channel msgs unaffected (passive still on)
assert mc_context_allows(cfg, _chan(channel=1), idx_to_name) is True
def test_passive_disabled_drops_channel_but_dm_still_respected():
cfg = MeshCoreContextConfig(enable_passive_context=False)
idx_to_name = {1: "#general"}
# channel msg dropped
assert mc_context_allows(cfg, _chan(channel=1), idx_to_name) is False
# DM still respects respond_to_dms (True by default) -> passes
assert mc_context_allows(cfg, _dm(), idx_to_name) is True