- mesh_data_store.py / env/store.py: make refresh() async, offload blocking
polls via asyncio.to_thread/gather so 7 lockstep sources no longer starve
the shared event loop.
- main.py: gather pollers concurrently + set_default_executor thread pool.
- Dockerfile / docker-compose.yml: healthcheck now curls the dashboard for a
real liveness signal instead of a process-exists check.
- transport/meshcore_transport.py: MeshCore keepalive loop (get_time() every
120s), reconnect re-arm (_post_reconnect_setup_async from
_on_connect_event), and MC channel-name normalization
(_resolve_mc_channel_idx strips a leading #).
- dashboard-frontend: MeshCoreConnection.tsx config-page hardening, new
ErrorBoundary component, wired into App.tsx.
- tests: fix ~40 call sites broken by refresh() becoming async (
test_generic_http.py, test_store_received_delta.py,
test_store_wzdx_persist.py) by wrapping with asyncio.run(), matching this
suite's existing convention for calling async code from sync test
functions. Verified: all 40 tests pass.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix(entrypoint): drop three phantom history keys from the seeded default
The config written on first boot -- what EVERY fresh Docker install starts
from -- seeded three keys that do not exist on HistoryConfig and are
silently discarded on load:
auto_cleanup: true
cleanup_interval_hours: 24
max_age_days: 30
HistoryConfig has only `database`, `max_messages_per_user`, and
`conversation_timeout`.
To be precise about the impact: history cleanup DOES work -- cleanup_expired()
is wired at main.py:237 and _prune_history() honours max_messages_per_user.
What never existed is the time-based retention model these keys describe (a
30-day age cutoff on a 24h interval). An operator setting max_age_days: 90
expecting 90-day retention was silently ignored.
Not implemented here -- whether time-based retention should exist is a
product decision, not a cleanup. This only stops the default config
promising it.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* docs: point onboarding at the dashboard, not the legacy example config
The documented setup path was `cp config.example.yaml config.yaml`, but that
file is ~40% incomplete: of the 20 top-level Config fields it covers 15,
omitting coverage (the universal bbox), danger_zones, generic_sources,
meshcore_context, commands, and the current notification model
(toggles/destinations/region_routes). Its own notifications header still says
the schema "will be replaced in v0.3 by the 8-toggle model" -- which shipped
long ago. Anyone following the project's own instructions landed on a
degraded surface with no signal a richer config existed.
It is also read by NOTHING at runtime: docker-entrypoint.sh sets
MESHAI_CONFIG=/data/config.yaml and writes its own inline default on first
boot. The Dockerfile still COPYs config.example.yaml into the image, so it is
kept and now labelled reference-only rather than a starting point.
- README: Docker quick-start seeds itself; configure via the dashboard. The
pip path still uses config.example.yaml (nothing seeds one there) but now
carries an honest note that it is a minimal bootstrap, not a reference.
Adds an Advanced section for the split /data/config/ layout and the
migrate_config_v03 path into it.
- docker-compose.yml: the comment claimed config lives at /data/config.yaml
as though that were the only layout; corrected to describe both, and note
secrets live in /data/secrets/.env.
- Dockerfile: document why config.example.yaml is still shipped.
Not done deliberately: config.example.yaml is NOT expanded to cover all 20
sections. A second hand-maintained schema is what caused this drift; the
dashboard is the authoritative surface. The legacy single-file loader is
untouched and still fully supported.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* docs(readme): move dashboard-first onboarding rewrite to root README
work/README.md is about to become a symlink to root README.md on
docs/single-readme. Retarget the Docker/dashboard-first quick start
and config.example.yaml honesty note added in be867d23 from
work/README.md to root README.md, adapted to the root file's own
Quick start structure (cd meshai/work, /work/-prefixed curl URLs), so
none of it is silently dropped when that symlink lands.
---------
Co-authored-by: Matt Johnson <mj@k7zvx.com>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
weather_watch and weather_advisory were falling through to the legacy
Mode-B renderer (composer.py), which appended the raw expiry epoch
(`exp 1783615500.0` from _context_segment) and overflowed 140 chars,
splitting into two packets with the epoch stranded in packet 2.
Fix: register both categories with the NWS formatter and gating decider
(same path as weather_warning/weather_statement), and add them to
MESHAI_CUTOVER_CATEGORIES in docker-compose.yml so the pipeline routes
them to the new formatter on deploy.
Co-authored-by: Matt Johnson <mj@k7zvx.com>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
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>
Preserve the live CT108 working-tree state (the running container is already
built and is unaffected by this commit).
Canonical build tree (work/ — what ships):
- work/meshai/config.py : ConnectionConfig watchdog reconnect knobs
- work/meshai/connector.py : watchdog link-state, socket-based liveness,
active_probe, in-place reconnect
- work/meshai/main.py : connection supervisor (watchdog) task
- work/Dockerfile : healthcheck also asserts /tmp/meshai.link=up
- work/docker-compose.yml : matching healthcheck change
Root tree (stale duplicate, earlier reconnect iteration — preserved for fidelity):
- meshai/config.py, meshai/connector.py, meshai/main.py
- Dockerfile, docker-compose.yml
New (root only, NOT under work/, currently unimported / not in build):
- meshai/notifications/pipeline/severity_router.py
Excluded: all *.bak / *.bak2 backup artifacts.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- 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>