CoreScope · Releases
from GitHub · updated 2026-06-2320 releases
v3.9.2 # 11 days ago · 2026-06-13 04:48 UTC
Upgrade urgency: Recommended. Big batch of operator-felt wins.
Highlights
Accessibility (#1668 — M2/M3/M4/M5 merged; M6 in flight)
- Two-tier CSS palette with semantic tokens (
--palette-*→--color-*/--text-*); operators can now hot-swap theme palettes - WCAG AA color-contrast hits across all 22 audited routes — 443 baseline violations → 0 verified by the new axe CI gate (#1696)
- Typography pass: 14px body / 12px+500 chip floor across the entire chrome (#1679)
- Hash-cell analytics palette, badge contrast, /live VCR controls (#1681)
- Audio-lab BPM + Volume sliders now have aria-labels
MQTT observability
- Per-source MQTT status endpoint + Observers panel (#1682) — connected/disconnected/last-packet/error counters per broker
- Mobile-friendly: cards at ≤640px instead of squished table (#1698)
Cold-load + analytics correctness
- Packet-store cold-load actually loads the database now (#1691). Pre-fix: after restart, only 0.3% of DB rows were loaded into RAM, dashboards looked empty for hours. Root cause: hot-load filtered by
first_seen(immutable per hash) instead of effective recency. Newtransmissions.last_seendenormalized column + writer-side update fixes this end-to-end. - Analytics endpoint returns 503 Retry-After during warm-up (#1688) — operators no longer see post-restart partial slices
- Warm-up banner surfaces backfill state in the UI (#1683)
Firmware 1.16 sync
- Server-side decoder parity for extended ACK payloads (#1695)
- Customizer toggle to hide 1-byte path hops (collide ~8-way at 2k nodes) — #1689
Performance
/packetsinit parallelizes loadObservers + loadPackets (#1693) — first-row time dropped from 25-40s to <5s on slow links- Confidence rating weighted by hash mode (#1687) — 1-byte sightings carry less weight than 6-byte
CI / operability
- OpenAPI completeness gate (#1678 Phase 1)
- Release fast-path: re-tag :edge → :vX.Y.Z when SHA matches (#1680) — most patch releases now skip the 30min full pipeline rerun
- Staging disk monitor + cleanup cron (#1686)
- Slideover E2E flake-gate reduced 20× → 3×, root cause fixed in #1693
Container image
ghcr.io/kpa-clawbot/corescope:v3.9.2(multi-arch: amd64, arm64)Verification
- 22 routes × 2 themes × 2 viewports automated a11y gate (axe-core, #1696)
- All 19 PRs ran through the full polish chain (parallel reviewers + Kent Beck TDD gate)
- Cold-load fix verified by
transmissions.last_seentest fixture (#1691)
Operator upgrade (MikroTik)
/container add remote-image=ghcr.io/kpa-clawbot/corescope:v3.9.2 envlist=cs-env mounts=cs-data,cs-caddyfile interface=veth-corescope start-on-boot=yes name=corescope-prod-v392Acknowledgements
External contributors carry over from v3.9.1.
- Two-tier CSS palette with semantic tokens (
v3.9.1 # 12 days ago · 2026-06-12 06:41 UTC
Upgrade urgency: Recommended — v3.9.1 is what v3.9.0 should have been. v3.9.0's container image never published (Playwright flake gated Docker build). v3.9.1 includes everything in v3.9.0 plus:
- WCAG AA contrast pass — new two-tier CSS palette (raw
--palette-*→ semantic--color-*/--text-*); muted-text family bumped to ≥4.5:1 in both themes (most well above —--text-mutedon dark surface goes 3.5 → 11.58:1). Operator-reported unknown-repeater chip ("dark-blue text on dark-blue background") fixed (2.75:1 → 4.95:1). Closes #1671. Partial fix for #1668. (#1676, f0addfda) - Slideover test stability —
test-slideover-1056-e2e.jswas racing the packets virtual-scroll spacer; tightened selectors, bumped data-row wait to 20s. Cleared the day-of-release CI flakes. Fixes #1662. (#1663+followups, f06359d7)
Verification
Test plan:
workspace-meshcore/test-plans/v3.9.0-cdp-test-plan.md(93 tests, applies unchanged to v3.9.1). M1 a11y audit:workspace-meshcore/a11y-audit/reports/violations-summary.md(2,429 BLOCKER → estimated 85% cleared by M2).Acknowledgements
External contributors from v3.9.0 still apply: @efiten, @EldoonNemar. No new external PRs since v3.9.0.
- WCAG AA contrast pass — new two-tier CSS palette (raw
v3.9.0 # 12 days ago · 2026-06-12 02:55 UTC
Upgrade urgency: Medium — fixes the post-restart "relay timelines empty" regression, surfaces silent
/api/nodestruncation, and ships operator-controlled per-name hiding.257 commits since v3.8.3 (72 substantive + 185 auto-generated coverage bumps). Every bullet ends with a commit SHA —
git show <sha>to verify.Highlights
- Your relay timelines survive a restart. Before v3.9.0, every container restart left repeater nodes with empty hop histories until live traffic replayed enough adverts to re-attribute. Now the relay-hop index is rebuilt from
path_jsonduring cold load — per-relay timelines, hop counts, and route stats are intact the moment the server says it's ready. (#1643, 938153dd) /api/nodesstops silently truncating at 500 rows. The hard cap was hiding nodes from the map, analytics and packets pages on any mesh of meaningful size — without any warning. Now properly paginated across every consumer, with internal UI requests bypassing the per-page clamp. (#1607, 26105748; #1637, 9002b25b; #1589, 7421ead9)- Hide your own node from a public dashboard with a prefix rename. New
hiddenNamePrefixesconfig (default["🚫"]) drops matching nodes from/api/nodes*while keeping DB rows for analytics — same convention other MeshCore dashboards already follow, no DB surgery, no permanent loss of history. (#1655, 825b2648) - Observer Compare is finally discoverable. The compare page existed before but was a hidden URL trick; now there are three entry points (header CTA, sticky selector strip, observer-table multi-select) leading into a Tufte-grade compare view with state-preserving selection. (#1642, 531bc8ac; #1645, c93ae67e; #1647, 167af54e)
- Per-node Reach. New
/api/nodes/{pubkey}/reach+ UI surfaces directional link quality per neighbor — answers "is my link to X any good in both directions" without staring at a topology graph. (#1627, e2212f50)
What's New
Observer Compare
- Promote observer comparison to first-class: header CTA, sticky selector strip, observer-table multi-select. (#1642, 531bc8ac)
- Tufte-grade compare page with themed button vocabulary + state-preserving multi-select across navigations. (#1645, c93ae67e)
- Polish: tightened checkboxes, hierarchy, selector strip + mobile fixes. (#1647, 167af54e)
- Wire
TableSorton the observers table with numeric/time column types so the sort affordance actually sorts. (#1641, d72ab69f)
Reach & Nodes
- Per-node Reach page +
GET /api/nodes/{pubkey}/reach(directional link quality). (#1627, e2212f50) - Paginate
/api/nodesacross map/live/analytics/packets/area-map so the 500-row server cap stops silently truncating UIs. (#1607, 26105748; #1637, 9002b25b) - Sortable First Seen column on the Nodes table. (#1587, 7533b3b6)
- Firmware
repeat:on|offhint now excludes listener-only observers from the disambiguator. (#1624, a4776557) - Link RTC-reset warnings on node detail to the offending packet hashes. (#1590, 1a2b8c48)
Analytics
- Relay Airtime Share endpoint + dumbbell chart. (#1601, 3898688d)
- 5-minute rolling-baseline anomaly detection for Write Sources. (#1593, a26a412c)
- TRACE packets overlay per-hop SNR on the path graph. (#1622, e9aed641)
- Multi-byte prefix repeaters now show up in the 1-byte hash-usage matrix view. (#1591, 3df89241)
Live & Map
- Fullscreen toggle on the live map + controls collapsed by default. (#1572, d7bd9d57)
- Colorblind simulation overlay (Brettel/Vienot) with reset-to-Wong button. (#1600, 571c960c)
- Path symbols legend disclosure on packets. (#1570, 5fd8900c)
- OSM / Stamen tile providers with per-provider Leaflet layer control. (#1533, d7cd9203)
- Operator-configurable
liveMap.maxNodes(default 2000). (#1577, 1bdb92de)
Config & Operator Surfaces
hiddenNamePrefixes(default["🚫"]) — drop matching nodes from/api/nodes*while preserving DB rows. (#1655, 825b2648)- Config-driven disabled-tabs list in the customizer modal. (#1579, 7292d60f)
- `branding …
- Your relay timelines survive a restart. Before v3.9.0, every container restart left repeater nodes with empty hop histories until live traffic replayed enough adverts to re-attribute. Now the relay-hop index is rebuilt from
v3.8.3 # 20 days ago · 2026-06-04 01:49 UTC
CoreScope v3.8.3
Upgrade urgency: High — contains a stored XSS fix. Public dashboards should upgrade immediately.
187 commits since v3.8.2 (47 substantive + 140 auto-generated coverage bumps). Every bullet ends with a commit SHA —
git show <sha>to verify.Highlights
- The live map is buttery now. Packet animations and node pulses moved off SVG-per-frame onto a hardware-accelerated canvas overlay — 60 FPS even when the mesh is firing on all cylinders. (#1490, 914f8694; #1521, 75a38f02)
- Dashboards and APIs feel snappier under load.
/api/observersp95 dropped from ~10.8s to sub-second on busy meshes,/api/statsis no longer re-scanning the observations table every 15s for the navbar, and per-observer analytics stopped re-parsing every timestamp three times. (#1483, 13bdee57; #1516, 38506001) - Stored XSS class closed — same class as CVE-2026-45323. Mesh-advertised node names and observer names were rendered to the DOM unescaped on app/nodes/observers/packets/live/analytics/route-view/area-map; a payload like
<img src=x onerror=…>executed. All sinks now escape; follow-up audits closed three more XSS sinks, an unbounded-limitDoS, and several log-injection paths. New CI gate hard-fails PRs that reintroduce either class. (#1537, f15b6779; #1539, 53339e08; #1540, 800d61c3; #1544, 7b430450; #1543, e4a21fc9) - Cross-domain embeds.
?embed=1on/#/mapand/#/channelsstrips the chrome, and a newCORS_ALLOWED_ORIGINSenv unlocks Home Assistant / Grafana panel embeds. (#1500, 367265eb) - The map's region filter now actually filters the map. Selecting a region hides non-region nodes by default; a "Show all nodes" toggle preserves the old behavior. (#1501, 28713fab)
- Observers with broken clocks now name themselves. Observers emitting zone-less timestamps get a ⚠️ chip on the observer list and a banner on detail with fix instructions — no more "why is this observer showing 1970?" support tickets. (#1480, 43b93c6b)
- Operators with big DBs no longer wait minutes for ingestor startup. Heavy index builds run in the background via a new async-migration runner; the ingestor accepts packets immediately on boot. (#1541, e438451d)
- Fresh deploys can decrypt #Public out of the box. Default Public channel key now ships in
channel-rainbow.json. (#897, 451b5e88)
HTML-escaped at:
app.jsglobal search dropdown (node + channel name);nodes.jstable row + Leaflet popups (×2);observers.jstable name cell;packets.jsobserver-name cells (×4) + multi-select checkbox label;live.jsnode-filter<option>+ map tooltip + hop popup;analytics.jstopology tooltip + RF-health aria-label;packets.jsCHAN hex decode fields;route-view.jshop + union tooltips (×2);area-map.htmlnode popups (×2). GlobalescapeHtmlnow covers the full 5-char OWASP set (& < > " ').map.jssafeEscwas a no-op identity since #48 — now wired to the real escaper. BackendsanitizeName()deliberately keeps< > " &for lossless storage /meshcore://deep-links; fix is at the sink per OWASP. Round-2 sweep added:traces.jsURL-fragment in popups,observer-detail.jsMQTT-meta tooltip,analytics.jsRF-healtharia-label. Pinned bytest-xss-escape-sinks.jsandtest-anl1-tooltip-render.jswith tag-injection and attribute-breakout payloads. (#1537, #1539)Features
- Hide non-region nodes by default on the live map when a region is selected; "Show all nodes" toggle restores legacy view; state in
localStorage['mc-region-show-all-nodes']. Fixes #1108. (#1501, 28713fab) ?embed=1on/#/mapor/#/channelssuppresses top-nav, bottom-nav, drawer; newcorsAllowedOriginsconfig +CORS_ALLOWED_ORIGINSenv;Access-Control-Allow-Methodstightened toGET, HEAD, OPTIONS. Fixes #1369. (#1500, 367265eb)- Customizer: marker stroke color/width/opacity tunable via Colors → Marker Stro …
v3.8.2 # 26 days ago · 2026-05-29 02:50 UTC
v3.8.2 — Mobile UX, Customizer, Performance
(Draft for operator review. ~3 weeks of work since v3.8.1, ~25 substantive commits.)
Headliners
- Mobile UX overhaul — packets surface redesigned mobile-first with single-row navbar, semantic-first detail panel, hidden chrome rail; nav controls (Favorites/Search/Customize) finally reachable on phones (#1471 — closes #1415/#1458/#1461/#1467/#1468/#1470).
- Customizer reframe — CB preset is now an end-user OPT-IN, not a force-override. Operator's
config.json nodeColorsare honored by default; per-user customizer override beats the preset (#1446/#1447/#1448/#1449 — fixes longstanding "config not honored" reports). - Dark-tile provider picker — 4 dark map variants pickable in customizer: Carto Dark, Esri Dark Gray, Voyager Inverted, Positron Inverted (#1420/#1430). Honored across all map surfaces including node-detail inset (#1470).
- Observer timestamps fixed — California (UTC-7) observers were perpetually 7h stale because naive timestamps were treated as UTC. Now:
observer.last_seenalways uses ingest time, per-packet rxTime keeps envelope time with symmetric clamp (#1463/#1464/#1465/#1466). - Cold-path cache —
/api/nodesno longer blocks on stale-cache rebuild; serves stale-while-revalidate (#1272/#1436).
Operator-facing changes
UI / UX
- New customizer toggle: Show encrypted channels (default OFF — finally a way to control the encrypted-channel flood without DevTools) (#1454/#1455)
- Traffic share label replaces the misleading "Usefulness" score (renamed everywhere; new
traffic_share_scoreJSON field added alongside the legacyusefulness_scorefor API compat). Tooltips explain both Traffic share and Bridge score with tap-to-toast on mobile (#1456/#1457) - Custom navbar logos no longer squished to 125×36 — proportional aspect preserved for square, tall, wide variants (#1450/#1451)
- Per-role color overrides now actually propagate to marker SVGs (was silently dropped before — #1438/#1439/#1441/#1443)
- "unknown" fake channel no longer accumulates in the channel list (live router was synthesizing it from undecryptable CHAN messages — #1468)
- Hash matrix marks 0x00 and 0xFF as reserved prefixes per MeshCore firmware keygen convention (@halo779 report — #1473/#1474)
- Mobile gesture hints — touch-only gate added (no longer show on desktop), width:fit-content fix for off-screen pills (#1065 chain)
Server / Performance
repeaterEnrichTTLcache cold-path now stale-while-revalidate; first request after expiry no longer blocks (#1272/#1436)- Hourly SQLite WAL checkpoint prevents unbounded WAL growth (#1435)
- Startup warning when
GOMEMLIMIT < 50% of container memory limit— prevents future OOMs (#1264/#1429) - WebSocket MQTT broker support (
ws:///wss://) documented + tested (#902) - Paths-through-node now sorted by recency (LastSeen DESC) with count as tiebreaker — was non-deterministic map iteration order (#1145/#1431)
Bug fixes
- Naive (zone-less) ISO timestamps in MQTT envelopes now symmetrically clamped (was: only future-skew rejected, past-skew passed through verbatim — California observers perpetually stale) (#1463/#1464)
- Observer.last_seen always uses ingest time, never envelope time (architectural cleanup, eliminates whole class of TZ bugs) (#1465/#1466)
- Hop-name mis-resolution on prefix collision — regression test added for #1144 (the production fix shipped earlier; this guards against re-introduction) (#1433)
- Channel-color-picker outside-click E2E flake fixed for real this time (race with
setTimeout(0)listener install — proper fix using rAF×2 + retry) (#1462) - CI: master runs no longer cancelled by subsequent commits (was dropping Deploy Staging silently — #1395/#1426)
- CSS parse-error: stray non-comment text after
#1062block was eating the next CSS rule from CSSOM (gesture-hint .width:fit-content wasn't taking effect for weeks) (#1065/#1453)
Community contributions this cycl
…
v3.8.1 # 28 days ago · 2026-05-27 08:29 UTC
CoreScope v3.8.1 — Route, Refined
Released 2026-05-27. Previous: v3.7.2 (2026-05-06). Supersedes v3.8.0 (released earlier today, before this perf fix landed).
⚡ Patch in v3.8.1:
/api/nodesTTL fixCommunity contribution from @efiten (#1425). The
repeaterEnrichTTLwas 15 seconds but the background recomputer runs every 5 minutes — so the cache was stale for 4m45s out of every 5min. On dense hop-graph deployments that meant 18s/api/nodescold-miss responses. TTL is now derived as2 * recomputer intervalso the cache stays warm between ticks. Measured 18.6s → ~0.5s on the contributor's prod mesh (analyzer.on8ar.eu).If you already deployed v3.8.0, just upgrade to v3.8.1 — same image semantics, drop-in.
Three weeks, 135 merged PRs. The headline is the Route Viewer redesign — packets and the paths they travel, finally rendered as one coherent story. Underneath: startup-window improvements for large DBs, a near-complete mobile chrome overhaul.
🗺️ NEW: Route Viewer
The new route viewer (
route-view.js+route-view.css) puts packet context, observer paths, and resolved hops in one sidebar — and reflows cleanly to a bottom sheet on mobile.- Packet context block in the sidebar header — per-type fact list (ADVERT name/role/sig/pubkey, DM src→dst with encryption state, GRP_TXT channel + decrypted preview, TRACE official-vs-observed hops, PATH src→dst with payload-source chips). Merges
pkt.decoded_json+obs.decoded_jsonand falls back to byte-levelraw_hexparsing for encrypted DMs. - Multi-path picker — chip per unique observer-path (
<count>/<total>+ hex hop string). Click to isolate; "All" renders an edge-deduplicated UNION view (each unique edge once, stroke weight = observer count). - Deep-link URLs —
#/map?packet=<hash>&obs=<id>. Bookmarkable, shareable, single source of truth. sessionStorage flow removed. - Hop resolution priority chain — server
resolved_path→ sharedHopResolver(observer-IATA-aware, same as packets page) → raw prefix. Kills a whole class of "route view named hops differently than packet detail" bugs. - Markers — uniform 22 px filled circle with seq number inside, hollow endpoint ring for SRC/DST, double concentric ring on SRC=DST loops, spider-fan for sub-14 px collisions debounced to
zoomend. - Colorblind-preset live colors —
routeRampper preset (viridis / plasma / pure-luminance) written to--mc-rt-ramp-0..4CSS vars and hot-recoloured oncb-preset-changed/theme-changed. - Desktop chrome — drag-to-resize sidebar persisted to localStorage, Material/Drive-style collapse chevron centred on the right edge.
- Mobile bottom sheet — anchored above bottom-nav + safe-area inset, thin summary line when collapsed (
TYPE · N hops · X km · M obs), expands to ~75 vh. All three legacy mobile detail panels closed on route entry.
Closes: #1418, #1419, #1422 · PR #1423
Related map work that landed this cycle: role-aware marker shapes + outline rings (#1334), WCAG 2.2 AA pass on cluster bubbles + role pills + multi-byte labels (#1357), thinner always-on marker outline (#1347).
⚡ Startup
- Hot startup window — new
packetStore.hotStartupHoursconfig loads only N hours of packet history synchronously and fills the rest in background daily chunks. Large DBs (multi-GB) no longer block the HTTP listener on a full-retention load before serving traffic (#1187). - Ingestor now owns the neighbor-graph and all schema migrations; the server is read-only. Removes a class of startup races where two processes raced the same migration (#1286, #1289).
🔬 NEW: Protocol decoder coverage
The MeshCore decoder now handles every documented payload type, with the legend updated end-to-end so operators see real names instead of
Type 6.- GRP_DATA + MULTIPART wire-format decoded (#12 …
- Packet context block in the sidebar header — per-type fact list (ADVERT name/role/sig/pubkey, DM src→dst with encryption state, GRP_TXT channel + decrypted preview, TRACE official-vs-observed hops, PATH src→dst with payload-source chips). Merges
v3.8.0 # Pre-release 28 days ago · 2026-05-27 08:11 UTC
⚠️ Superseded by v3.8.1 — released the same day with a community-contributed
/api/nodesTTL fix (#1425, @efiten). Use v3.8.1 instead.
CoreScope v3.8.0 — Route, Refined
Released 2026-05-27. Previous: v3.7.2 (2026-05-06).
Three weeks, 135 merged PRs. The headline is the Route Viewer redesign — packets and the paths they travel, finally rendered as one coherent story. Underneath: startup-window improvements for large DBs, a near-complete mobile chrome overhaul.
🗺️ NEW: Route Viewer
The new route viewer (
route-view.js+route-view.css) puts packet context, observer paths, and resolved hops in one sidebar — and reflows cleanly to a bottom sheet on mobile.- Packet context block in the sidebar header — per-type fact list (ADVERT name/role/sig/pubkey, DM src→dst with encryption state, GRP_TXT channel + decrypted preview, TRACE official-vs-observed hops, PATH src→dst with payload-source chips). Merges
pkt.decoded_json+obs.decoded_jsonand falls back to byte-levelraw_hexparsing for encrypted DMs. - Multi-path picker — chip per unique observer-path (
<count>/<total>+ hex hop string). Click to isolate; "All" renders an edge-deduplicated UNION view (each unique edge once, stroke weight = observer count). - Deep-link URLs —
#/map?packet=<hash>&obs=<id>. Bookmarkable, shareable, single source of truth. sessionStorage flow removed. - Hop resolution priority chain — server
resolved_path→ sharedHopResolver(observer-IATA-aware, same as packets page) → raw prefix. Kills a whole class of "route view named hops differently than packet detail" bugs. - Markers — uniform 22 px filled circle with seq number inside, hollow endpoint ring for SRC/DST, double concentric ring on SRC=DST loops, spider-fan for sub-14 px collisions debounced to
zoomend. - Colorblind-preset live colors —
routeRampper preset (viridis / plasma / pure-luminance) written to--mc-rt-ramp-0..4CSS vars and hot-recoloured oncb-preset-changed/theme-changed. - Desktop chrome — drag-to-resize sidebar persisted to localStorage, Material/Drive-style collapse chevron centred on the right edge.
- Mobile bottom sheet — anchored above bottom-nav + safe-area inset, thin summary line when collapsed (
TYPE · N hops · X km · M obs), expands to ~75 vh. All three legacy mobile detail panels closed on route entry.
Closes: #1418, #1419, #1422 · PR #1423
Related map work that landed this cycle: role-aware marker shapes + outline rings (#1334), WCAG 2.2 AA pass on cluster bubbles + role pills + multi-byte labels (#1357), thinner always-on marker outline (#1347).
⚡ Startup
- Hot startup window — new
packetStore.hotStartupHoursconfig loads only N hours of packet history synchronously and fills the rest in background daily chunks. Large DBs (multi-GB) no longer block the HTTP listener on a full-retention load before serving traffic (#1187). - Ingestor now owns the neighbor-graph and all schema migrations; the server is read-only. Removes a class of startup races where two processes raced the same migration (#1286, #1289).
🔬 NEW: Protocol decoder coverage
The MeshCore decoder now handles every documented payload type, with the legend updated end-to-end so operators see real names instead of
Type 6.- GRP_DATA + MULTIPART wire-format decoded (#1280) — group-data sensor payloads + multipart fragmentation are no longer opaque blobs.
- CONTROL flags +
advertRolefix — accurate role attribution on ADVERTs from edge cases that were previously misclassified (#1280). payloadTypeNamestable centralised, exposed at the API + UI; new TransportCodes / Feat1 / Feat2 enums; RAW_CUSTOM type plumbed through; sensor-payload docs published (#1291).
🆕 NEW: Operator features
- Repeater "usefulness score" (bridge axis) — secon …
- Packet context block in the sidebar header — per-type fact list (ADVERT name/role/sig/pubkey, DM src→dst with encryption state, GRP_TXT channel + decrypted preview, TRACE official-vs-observed hops, PATH src→dst with payload-source chips). Merges
v3.7.2 # 2 months ago · 2026-05-06 19:33 UTC
v3.7.1 # 2 months ago · 2026-05-05 00:08 UTC
Identical to v3.7.0 + live PSK decrypt fix (#1031). This is the tagged release with the Docker image on GHCR.
docker pull ghcr.io/kpa-clawbot/corescope:v3.7.1See v3.7.0 release notes for full changelog.
v3.7.0 # 2 months ago · 2026-05-04 05:13 UTC
CoreScope v3.7.0 — "Operator's Toolkit"
50 commits · 30 issues closed · 95 files changed · +7,710 / -293 LOC
Channel Decryption
Rebuilt from scratch. Pure-JS AES-128-ECB + SHA-256/HMAC — works on HTTP and HTTPS. PSK channels: add by key, name them, delete them, see decrypt count. Live WebSocket auto-decrypt with unread badges. Channel hash-byte dedup fixed.
Analytics
Selectable timeframes (1h/24h/7d/30d). Region attribution by repeater home, not observer. "All" filter fixed. Topology dedup (no more triple-counted repeaters). Timestamp format applied to chart axes.
New Surfaces
/#/roles— role distribution + per-role clock-skew/api/backup— API-key gated SQLite export via VACUUM INTO/api/healthz— readiness probe
Packets & Filters
Transport route type filter (
transport == true,T_FLOOD,T_DIRECT). Clear filters button. Scroll position preserved. Null hash/timestamp packets cleaned. GRP_DATA type 6 support.Map & Nodes
Short pubkey-prefix URLs (📡 Copy short URL). Multi-byte hash support indicator on markers. Channel color picker fixed.
MQTT & Ingestor
Async path_json backfill (no more startup blocks). Per-source
regionfield. MQTT startup resilience (unreachable sources don't block). Configurable connect timeout. Observer IATA whitelist + blacklist. Per-hop TRACE SNR extraction in ingestor.Observer & Node
Flood/direct filter on comparison page. TRACE per-hop SNR in byte breakdown. Clock skew evidence UI. Separate last-status vs last-packet timestamps. Soft-deleted observers excluded. Export/import includes favorites.
Infra
CORS allowlist. RW SQLite connection cache. GeoFilter Builder draft save/load/download. False-positive path exclusion.
Upgrade: New config fields in
config.example.json:observerIATAWhitelist,observerBlacklist, per-sourceregion/connectTimeoutSec, CORSallowedOrigins. One-time DB migration runs async on startup.
⚠️ Docker image not available for this tag (CI trigger issue). Use v3.7.1 for the container image:
ghcr.io/kpa-clawbot/corescope:v3.7.1v3.6.0 # 2 months ago · 2026-05-01 09:25 UTC
v3.6.0 - The Forensics
CoreScope just got eyes everywhere. This release drops path inspection, color-by-hash markers, clock skew detection, full channel encryption, an observer graph, and a pile of robustness fixes that make your mesh network feel like it's being watched by someone who actually cares.
134 commits, 105 PRs merged, 18K+ lines added. Here's what shipped.
🚀 New Features
Path-Prefix Candidate Inspector (#944, #945)
The marquee feature. Click any path segment and CoreScope opens an interactive inspector showing every candidate node that could match that hop prefix - plotted on a map with scoring by neighbor-graph affinity and geographic centroid. Ambiguous hops? Now you can see why they're ambiguous and pick the right one.
Why you'll love it: No more guessing which
0xA3is the real repeater. The inspector lays out every candidate, scores them, and lets you drill in visually.Color-by-Hash Packet Markers (#948, #951)
Every packet type gets a vivid, hash-derived color - on the live feed, map polylines, and flying-packet animations. Bright fill with dark outline for contrast. No more monochrome blobs - you can visually track packet flows by color at a glance.
Node Filter on Live Page (#924, #771)
Filter the live packet stream to show only traffic flowing through a specific node. Pick a repeater, see exactly what it's carrying. That simple.
Clock Skew Detection (#746, #752, #828, #850)
Full pipeline: backend computes drift using Theil-Sen regression with outlier rejection (#828), the UI shows per-node badges, detail sparklines, and fleet-wide analytics (#752). Bimodal clock severity (#850) surfaces flaky-RTC nodes that toggle between accurate and drifted - instead of hiding them as "No Clock."
Why you'll love it: Nodes with bad clocks silently corrupt your timeline. Now they glow red before they ruin your analysis.
Observer Graph (M1+M2) (#774)
Observers are now first-class graph citizens. CoreScope builds a neighbor graph from observation overlaps, scores hop-resolver candidates by graph edges (#876), and uses geographic centroid for tiebreaking. The observer topology is visible and queryable.
Channel Encryption - Full Stack (#726, #733, #750, #760)
Three milestones landed as one: DB-backed channel message history (#726), client-side PSK decryption in the browser (#733), and PSK channel management with add/remove UX and message caching (#750). Add a channel key in the UI, and CoreScope decrypts messages client-side - no server-side key storage. The add-channel button (#760) makes it dead simple.
Why you'll love it: Encrypted channels are no longer black boxes. Add your PSK, see the messages, search history - all without exposing keys to the server.
Hash Collision Inspector (#758)
The Hash Usage Matrix now shows collision details for all hash sizes. When two nodes share a prefix, you see exactly who collides and at what size.
Geofilter Builder - In-App (#735, #900)
The geofilter polygon builder is now served directly from CoreScope with a full docs page (#900). No more hunting for external tools. Link from the customizer, draw your polygon, done.
Node Blacklist (#742)
nodeBlacklistin config hides abusive or troll nodes from all views. They're gone.Observer Retention (#764)
Stale observers are automatically pruned after a configurable number of days. Your observer list stays clean without manual intervention.
Advert Signature Validation (#794)
Corrupt packets with invalid advert signatures are now rejected at ingest. Bad data never hits your store.
Bounded Cold Load (#790)
Load()now respects a memory budget - no more OOM on cold start with a fat database. Combined with retention-hours cutoff (#917), cold start is safe on constrained hardware.Multi-Arch Docker Images (#869)
Official images now publish
amd64+arm64in a single multi-arch manifest. Raspberry Pi operators: pull and run. No specia …v3.5.2 # 2 months ago · 2026-04-13 05:49 UTC
CoreScope v3.5.2
🔧 New:
corescope-decryptCLIStandalone tool for retroactive channel message decryption. Decrypt hashtag channel messages from your SQLite database — even packets ingested before the channel key was configured.
corescope-decrypt --channel "#test" --db meshcore.db --format irc corescope-decrypt --channel "#wardriving" --db meshcore.db --format json --output messages.json corescope-decrypt --channel "#sf" --db meshcore.db --format html --output viewer.htmlThree output formats: JSON (full metadata), HTML (interactive viewer), IRC/log (greppable plain text). Included in the Docker image at
/app/corescope-decrypt. Only works with hashtag channels (#name) — thepublicchannel uses a pre-shared key, not hashtag derivation.⚡ Eviction redesign
Replaced
HeapAlloc-based eviction with self-accountingtrackedBytescounter. No more cascading eviction on large databases. High/low watermark hysteresis + 25% safety cap.📊 Cache invalidation tuning
Wired the
invalidationDebounceconfig (was dead code). Default cooldown 10s → 300s. Collision cache only clears on new nodes. Expected cache hit rate: 7% → 50-80%.🔬 Multi-byte capability
Repeater multi-byte detection integrated into Hash Adopters table. Filter buttons fixed. TRACE packets excluded from suspected detection (pre-1.14 repeaters can forward multi-byte TRACEs).
🐛 Fixes
- Relay-only nodes now appear as alive (path hop indexing + debounced DB touch)
- Live feed timestamps refresh every 10s (no more stale "10s ago")
- Perf page shows tracked memory, not heap
- SQLite busy_timeout on all write connections
- Dockerfile includes
internal/sigvalidate
Downloads
corescope-decrypt-linux-amd64— x86_64 static binarycorescope-decrypt-linux-arm64— ARM64 static binary (Raspberry Pi, etc.)
Upgrade
docker pull ghcr.io/kpa-clawbot/corescope:v3.5.2v3.5.0 # 3 months ago · 2026-04-08 07:27 UTC
CoreScope v3.5.0 🚀
The "stop building from source and start analyzing your mesh" release. 95 commits.
🐳 Pre-built Docker Images
CoreScope now ships as a ready-to-run Docker image on GitHub Container Registry. No cloning, no building, no dependencies — just pull and run.
docker run -d --name corescope -p 80:80 -p 443:443 -p 1883:1883 \ -v corescope-data:/app/data \ ghcr.io/kpa-clawbot/corescope:v3.5.0Using HTTPS with a custom domain? Mount your Caddyfile and certs directory:
docker run -d --name corescope -p 80:80 -p 443:443 -p 1883:1883 \ -v /your/data:/app/data \ -v /your/Caddyfile:/etc/caddy/Caddyfile:ro \ -v /your/caddy-data:/data/caddy \ ghcr.io/kpa-clawbot/corescope:v3.5.0Caddy auto-provisions Let's Encrypt certs. Your Caddyfile just needs:
yourdomain.example.com { reverse_proxy localhost:3000 }That's it. Zero config required — MQTT broker, Caddy HTTPS, and SQLite are built in.
Already running CoreScope?
# 1. Find your running container name docker ps --format '{{.Names}}' # 2. Stop and remove it docker stop <container-name> && docker rm <container-name> # 3. Pull the pre-built image docker pull ghcr.io/kpa-clawbot/corescope:v3.5.0 # 4. Run with your existing data directory docker run -d --name corescope -p 80:80 -p 443:443 -p 1883:1883 \ -v /your/data:/app/data \ -v /your/Caddyfile:/etc/caddy/Caddyfile:ro \ -v /your/caddy-data:/data/caddy \ ghcr.io/kpa-clawbot/corescope:v3.5.0Your data volume stays. Nothing to migrate.
Tags:
v3.5.0(this release) ·latest(latest tagged release) ·edge(master tip, for testing). Env:DISABLE_CADDY=true/DISABLE_MOSQUITTO=trueif you bring your own.
⚡ 83% Faster
35 performance commits. Packets endpoint p50 dropped from 16.7ms → 2.7ms. Server now serves HTTP within 2 minutes on any DB size — async background backfill means you're never staring at a loading screen. N+1 API calls killed everywhere. Prefix map memory cut 10x. WebSocket renders batched via rAF.
🔬 RF Health Dashboard
New Analytics tab. Per-observer noise floor as color-coded columns (green/yellow/red), airtime utilization, error rates, battery levels. Click any observer for the full breakdown. Region-filterable. This is the beginning of making CoreScope more than just a packet viewer.
🗺️ See Where Traces Actually Go
Send a trace → watch it on the live map. Solid animated line shows how far it got. Dashed ghost shows where it didn't reach. Finally know where your trace failed, not just that it failed.
📊 Things That Were Lying To You
- "By Repeaters" was counting companions. Fixed.
- Zero-hop adverts claimed "1 byte hash" when the hash size was unknowable. Fixed.
- "Packets through this node" showed packets through a different node with the same prefix. Fixed — now uses the neighbor affinity graph.
- Table sorting on nodes/neighbors/observers silently did nothing. Fixed.
🔗 Deep Links · 🎨 Channel Colors · 📱 Mobile · 🔑 Security
Deep links — every page state goes in the URL. Share a link to a specific node, filter, or analytics tab.
Channel colors — click the color dot next to any channel, pick from 8 colors, see it highlighted across the feed. Persists in localStorage.
Distance units — km, miles, or auto-detect from locale. Customizer → Display.
Mobile — 44px touch targets, ARIA labels, responsive breakpoints.
Security — weak API keys rejected at startup. License: GPL v3.
📡 Full API Documentation
Every endpoint is now documented with an auto-generated OpenAPI 3.0 spec — always in sync with the running server.
- Interactive Swagger UI: analyzer.00id.net/api/docs — browse and test all 40+ endpoints
- Machine-readable spec: analyzer.00id.net/api/spec — import into Postman, Insomnia, or use for bot/integration development
On your own instanc …
v3.4.1 # 3 months ago · 2026-04-04 08:36 UTC
v3.4.1 — Server-Side Hop Resolution & Performance
⭐ Headline: Resolved Paths
Hop prefixes are now resolved to full node identities at ingest time using a persisted neighbor affinity graph. No more guessing — the server knows which "D6" is which.
- Persisted neighbor graph (
neighbor_edgestable) — builds automatically on first run, loads instantly on restart. 4-tier resolution: affinity → geo → GPS → first match. resolved_pathon every observation — full 64-char pubkeys stored alongside rawpath_json. Ambiguous prefixes that the old client-side resolver got wrong are now correct.- Frontend prefers server-resolved paths — packets page, live map, Show Route, packet detail all use
resolved_pathwith automatic fallback for old data. - Zero-cost on subsequent startups — edges and resolved paths persist in SQLite. First run does a one-time backfill.
🚀 Performance
- Distance index rebuild debounced — was triggering on every ingest cycle, now at most every 30s. Eliminates CPU hot loop on busy meshes.
- Neighbor graph build optimized — cached
strings.ToLower, cached JSON parsing viasync.Once, TTL bumped 60s → 5min. Cuts cold start time. - O(n²) observation dedup → O(n) — map-based replacement.
- O(n²) selection sort → sort.Slice — standard library sort.
- Parallelized expanded group fetches — hashIndex Map lookup instead of linear scan.
- Advert pubkey tracking incremental — eliminates per-request JSON parsing.
- Rate-limited cache invalidation — prevents 0% hit rate under sustained ingest.
- VCR replay chunked — prevents UI freezes on large replays.
🐛 Fixes
hasResolvedPathflag race —detectSchema()ran before column was added, causing full re-backfill on every restart. Fixed.resolved_pathmissing from grouped packets —groupByHash=trueresponse wasn't including resolved paths. Fixed.- Memory leak in
pruneStaleNodes—nodeActivitymap entries never cleaned up. Fixed. - iOS tap-to-scroll broken — scroll container restructured for status bar tap.
- Observer filter dropped groups — grouped packets view lost groups when observer filter was active. Fixed.
- Null crash on ADVERT detail —
pathHopsnull guard added. - Mobile filter dropdown — CSS specificity prevented expansion. Fixed.
- Hash collision analysis — now only counts repeaters, not all nodes.
- Virtual scroll height — accounts for expanded group rows.
🔧 Infra
- CI build and deploy jobs pinned to
meshcore-vmrunner. - Deep linking rule added to AGENTS.md.
⚠️ Known Issues / Heads Up
- First restart after upgrade takes ~4 minutes on large DBs — The server builds the neighbor graph (~40s), then backfills
resolved_pathfor all existing observations (~2 min), plus normal data load (~35s). With 1M+ observations, expect heavy CPU and SQLite locking during this window. MQTT ingest may disconnect and reconnect. This is a one-time cost — subsequent restarts load from SQLite in ~15s. Do not restart the server during this process or it will start over.
📊 By the Numbers
- 22 commits, 13 files changed
- 1.1M observations backfilled with resolved paths
- 408K neighbor edges persisted
- Cold start: ~4 min first run → ~15s subsequent
- Persisted neighbor graph (
v3.4.0 # 3 months ago · 2026-04-03 08:56 UTC
CoreScope v3.4 Release Notes
The neighbor affinity release. CoreScope now understands how nodes relate to each other — not just that they exist, but how strongly they're connected. This powers smarter hop resolution, richer node detail pages, and a new graph visualization in analytics.
🎯 Features
Neighbor Affinity System (7 milestones)
A complete neighbor relationship engine, from backend graph building to frontend visualization:
- Affinity graph builder — computes neighbor relationships and connection strength from packet traffic (#507)
- Affinity API endpoints — REST endpoints to query neighbor data (#508)
- Show Neighbors via affinity API — the existing Show Neighbors feature now uses real affinity data instead of raw packet heuristics (#512, fixes #484)
- Affinity-aware hop resolution — hop resolver uses neighbor affinity to pick better paths (#511)
- Node detail neighbors section — dedicated neighbors panel on the node detail page (#510)
- Affinity debugging tools — inspect and troubleshoot affinity calculations (#521)
- Neighbor graph visualization — interactive neighbor graph in the analytics tab (#513)
Customizer v2
- Event-driven state management replaces the old imperative approach — cleaner, more predictable theme/config updates (#503)
🐛 Bug Fixes
- Stale parsed cache on observation packets — observation packets now correctly invalidate the JSON parse cache (#505)
- Null-guard rAF callbacks — live page no longer crashes when
requestAnimationFramecallbacks fire after cleanup (#506) - Customizer v2 phantom overrides — fixed phantom config entries, missing defaults, and stale dark mode state (#520)
- Neighbor affinity empty results — fixed pubKey field name mismatch causing empty affinity graphs (#524)
- Home defaults in server theme — server-side theme config now includes home page defaults (#526)
- Neighbor UI crash + dark mode — fixed Show Neighbors crash and improved dark mode contrast (#527)
- Home page steps + FAQ — both steps AND FAQ now render correctly on the home page (#529)
⚡ Performance
- Cached JSON.parse for packet data — packet payloads are parsed once and cached, avoiding redundant
JSON.parsecalls on repeated access (#400)
Known Limitations
- Affinity graph scales with traffic volume — networks with very low packet rates may show weak or missing neighbor relationships until enough data accumulates
- Debugging tools are developer-facing — the affinity debug panel (#521) is functional but not polished for end-user consumption
- Customizer v2 migration — custom themes saved under v1 may need to be re-applied after upgrade
v3.3.0 # 3 months ago · 2026-04-02 07:16 UTC
CoreScope v3.3.0 — Performance & Polish
28 changes since v3.2.0. This release is all about making CoreScope faster, more accurate, and harder to break.
⚡ Performance Overhaul
- Virtual scroll for packets table — handles 10K+ packets without breaking a sweat (#402)
- Observer Map lookups — O(1) instead of linear scans on every render (#468)
- requestAnimationFrame animations — live page ditches setInterval, no more stacking when tab-switching (#470)
- Client-side My Nodes filter — instant toggle, zero server round-trips (#401)
- Server-side collision analysis — heavy computation moved off the browser (#415)
- In-place ADVERT upserts — node updates without full page reload (#461)
- Targeted cache invalidation — analytics refresh only what changed (#379)
- Faster /api/packets and /api/channels — query optimizations for large stores (#328)
🗺️ New Features
- Show Direct Neighbors — click any node on the map to filter to its 1-hop neighbors (#480)
- Auto cache busters —
__BUST__placeholders replaced at startup, no more merge conflicts over timestamps (#481) - Release tag pinning —
manage.sh update v3.3.0pins to exact versions (#456)
🔧 Fixes
- Haversine distances — hop distances now use proper great-circle math instead of flat-earth approximation (#478)
- Observer online status — packet ingestion updates last_seen, fixing false "offline" flags (#479)
- Hash collision region filter — analytics respects region selection (#477)
- Channel hash display — shows hex (0x1A) instead of confusing decimal (#471)
- VCR timezone — timeline and clock respect UTC/local toggle (#459)
- Graceful shutdown — WAL checkpoint on container stop (#453)
- PerfStats data race — mutex synchronization eliminates concurrent access bugs (#469)
- Multiple null-guard fixes — no more crashes on navigate-away (#454, #462)
- Score/direction extraction — MQTT fields properly parsed with unit stripping (#371)
- Config reset — no more SITE_CONFIG contamination after reset (#460)
- Staging config — always refreshes from prod (#467)
📊 By the Numbers
- 28 merged PRs
- 8 performance improvements
- 13 bug fixes
- 3 new features
- 2 CI/docs improvements
- ~1,400 lines added across 23 files
v3.2.0 # 3 months ago · 2026-04-01 06:40 UTC
A stability and data integrity release — fixing critical crashes, broken observer data, and WebSocket storms, while adding transport code visibility, geo-filtering, and a fully responsive navigation bar.
15 commits · 35 files · +1,659 / -229 · 3 contributors · 9 issues closed
🐛 Critical Fixes
Packets page crash on mobile (#326) —
Number(null)silently set the time window to "All time", loading 50K+ packets and crashing iOS Safari/Edge. Fixed with proper guard, 1K packet cap on mobile, and restricted time window options.Observer metadata broken since Go migration (#320, #321) — Battery, uptime, noise floor read at wrong nesting level; SNR/RSSI case-sensitive with no fallback. All fields now work with 10 regression tests.
Hash stats frozen on historical data (#303) — Reconfigured repeaters now show updated hash size immediately instead of requiring dozens of adverts to shift the statistical mode.
WebSocket replaying entire database (#349) — Corrupted DB caused the poller to start from ID 0, broadcasting 76K packets at ~2,000/sec. Poller now cross-checks against the in-memory store.
✨ Features
Transport code badge (#241) — "T" badge on packets using
T_FLOOD/T_DIRECTrouting — instant visibility into transport adoption across the mesh.Geo-filter enforcement (#215) — Server-side polygon boundary filtering, automatic DB pruning for out-of-bounds nodes, and a visual geofilter-builder tool.
Responsive navigation (#322) — Hamburger menu at ≤1023px with drawer scroll, outside-click close, Escape key, and
aria-expanded. Priority+ pattern on tablets (768–1023px): 5 primary tabs inline + "More ▾" dropdown for the rest.🎨 Improvements
Side pane contrast (#334) — Card-style backgrounds on node detail side pane sections, fixing poor dark mode contrast.
WebSocket time filtering — WS packets now respect the selected time window instead of showing data from days ago.
CI runner cleanup (#333) — Automatic Docker image pruning after deploy. Runner was 100% full (18.8GB of dangling images).
Full changelog:
v3.1.0...v3.2.0v3.1.0 # 3 months ago · 2026-03-31 08:01 UTC
152 commits · 240 files changed · 51K insertions / 55K deletions · 44 PRs merged · 7 contributors
Highlights
Timestamp Controls — Toggle between relative ("3m ago") and absolute timestamps across all pages. UTC/local timezone, format presets, admin-configurable defaults. New Display tab in customizer.
Channel Region Filtering — Multi-region deployments no longer show cross-region message crosstalk. Server-side SQL prefilter + frontend WS guard.
Disable Internal Mosquitto —
DISABLE_MOSQUITTO=truein.envskips the built-in broker for users running their own.Port Negotiation —
manage.sh setupdetects port conflicts, suggests alternatives, persists choices to.env.Packet Store Eviction — In-memory packet store now has configurable memory limits with LRU eviction to prevent OOM on long-running instances.
Telemetry Decoding — Battery voltage and temperature decoded from sensor adverts and displayed on node detail pages.
Observer Identity — Go ingestor now persists model, firmware, client version, and radio from observer status messages.
Hash Size Analytics — Dominant hash size computation (mode, not last-seen), multi-byte hash usage matrix with stats and tooltips, distribution by repeaters.
Performance — O(n) slice prepend eliminated on packet ingest, parallel coverage collector (8min → 30-60s), packet store query optimizations.
Decoder Alignment — Packet decoder aligned with MeshCore firmware spec, TRACE path hops parsed correctly.
manage.sh Overhaul — Compose-only wrapper,
.envas single source of truth, config migration from repo root to data directory, CRLF auto-fix, tilde expansion.CI/CD — Go server E2E tests replace Node.js, Playwright suite,
workflow_dispatchfor manual triggers, repo-wide LF normalization with blame history preservation.Mobile Responsive — Live bottom-sheet, perf layout, nodes column hiding for small screens.
Configurable Health Thresholds — Node health thresholds now configurable in hours via config.json.
pprof Profiling — Optional pprof endpoints controlled by
ENABLE_PPROFenv var for production debugging.Breaking Changes
- Config location moved —
config.jsonnow lives in the data directory (~/meshcore-data/config.json).manage.shauto-migrates from repo root onupdate/start. - Node.js backend fully removed — all backend code is Go. If you were running the Node.js server directly, you must switch to the Docker image.
Known Issues
- FAQ/home page customizer editing can cause steps to disappear — needs architectural fix (#284, #325)
- Observer telemetry (battery, uptime, noise_floor) reads wrong JSON level (#320)
- SNR/RSSI case-sensitive key lookup may miss lowercase variants (#321)
- Repeater hash stats may not update after hash size change (#303)
- VCR timeline ignores UTC/local timezone setting (#324)
- Top nav bar controls invisible on narrow screens (#322)
- Config location moved —
v3.0.0 # 3 months ago · 2026-03-28 08:56 UTC
v3.0.0 — The Go Rewrite
MeshCore Analyzer is now powered by Go. The entire backend — MQTT ingestion, packet decoding, API server, WebSocket broadcast — has been rewritten from Node.js to Go. Same features, same UI, same database. Dramatically faster.
This is the biggest change in the project's history. Over 200 commits, 58 issues closed, and a ground-up reimplementation that delivers real, measurable performance gains on every endpoint.
⚡ Performance
These are real numbers from production with 56K+ packets:
Endpoint Node.js Go Packet queries 30-100ms sub-millisecond (in-memory store) GroupByHash 437ms (9s before store) 97ms Analytics (RF, topology, distance) 1-8 seconds all under 100ms Node health calculation 13 seconds instant (precomputed) Server startup (56K packets) ~9 seconds < 1 second Memory (56K packets) ~1.3 GB ~300 MB The Go server loads all packets into an in-memory store at startup and serves queries directly from RAM. Analytics are precomputed at ingest time — no more scanning the full packet table on every request. TTL caches protect expensive aggregations. The result: every page in the UI feels instant.
🆕 New Features
Protobuf API Contract
10
.protofiles define the exact shape of all 40+ API endpoints and WebSocket messages. Golden fixture tests ensure the Go server matches the Node.js response format byte-for-byte. API drift is caught in CI before it reaches production.Go Runtime Metrics
The performance page now shows Go-specific runtime stats when connected to a Go backend: goroutine count, heap allocation, GC pause percentiles, and memory breakdown. The engine badge in the stats bar shows [go] or [node] so you always know which backend you're running.
Build Identity
Every API response from
/api/statsand/api/healthnow includesengine,version,commit, andbuildTimefields. The stats bar in the UI shows the commit hash as a clickable link to the exact source.Observer Packet Comparison (#129)
New
#/comparepage lets you compare what different observers saw for the same packet — side-by-side diffs of paths, timestamps, and signal data.Auto-Updating Nodes List
The Nodes tab now updates in real-time when ADVERT packets arrive via WebSocket. No more manual refresh to see new nodes.
Channel Improvements
- Channel hash displayed for undecrypted GRP_TXT messages — you can see which channel even without the key
- Sortable channels table with persistent column sort preferences
- Garbage decryption detection — wrong keys no longer produce garbled "decrypted" text
- AES-128-CTR channel decryption natively in Go
Node Pruning (#202)
Nodes past the retention window are automatically moved to an
inactive_nodestable instead of polluting the active node list. Pruning runs hourly.Correct Advert Counts
Advert counts now reflect unique transmissions, not total observations. A packet seen by 8 observers counts as 1 advert, not 8.
🐛 Bug Fixes
- Phantom nodes from hop prefixes (#133) —
autoLearnHopNodesno longer creates fake nodes from 1-byte repeater IDs. Active node counts, live page counter, and topology analytics all filtered to real nodes only. - Offline nodes on map (#126) — ambiguous hop prefixes excluded from path-seen tracking. Stale nodes dim on the live map instead of disappearing.
- Disappearing live map nodes (#130) — stale nodes are dimmed, not removed, preventing the jarring vanish-and-reappear cycle.
- packetsLastHour always zero (#182) — early
breakin observer loop prevented counting; fixed across all observers. - Corrupted packet decoder crash (#183) — bounds check on path hops prevents buffer overrun on malformed packets.
- Node detail rendering crashes (#190) —
Number()casts andArray.isArrayguards …
v2.9.0 # 3 months ago · 2026-03-25 22:50 UTC
🗜️ Database Optimization
The observations table has been completely rearchitected for efficiency. Existing instances migrate automatically on startup — no manual action needed.
- 70% smaller database — 478MB → 141MB on a real-world instance with 950K observations
- Observer IDs stored as integer foreign keys instead of 64-char hex strings repeated per row
- Redundant columns removed (
hash,observer_name,created_at) - Timestamps stored as epoch integers instead of ISO strings
- In-memory dedup Set prevents expensive unique index lookups during ingestion
Migration Safety
- Timestamped backup created automatically before migration (never overwrites previous backups)
- Detects already-migrated databases (safe to restart mid-migration)
- Falls back gracefully if migration fails — original data preserved
packets_vview maintains full backward compatibility — no frontend changes needed
WAL Management
- Daily
TRUNCATEcheckpoint at 2:00 AM UTC reclaims WAL file space VACUUM+TRUNCATEcheckpoint runs after migration completes
📊 SQLite Observability
New section in the Performance Dashboard (
#/perf) showing:- DB file size, WAL size, freelist (wasted space)
- Row counts for all tables
- WAL busy pages — shows if checkpointing is keeping up
- Color-coded thresholds (green/yellow/red)
🐛 Bug Fixes
disambiguateHops: Restoredknownfield that was dropped during a refactor — hop resolution in analytics now correctly reports whether a prefix was resolved- E2E tests: Fixed channel message queries for
#hashtagchannels (missing URL encoding) - MQTT in tests: Tests no longer connect to real MQTT brokers —
NODE_ENV=testskips MQTT setup
🏗️ Setup & Operations
manage.sh— new idempotent setup wizard for Docker deployments- Backup/restore now includes config, Caddyfile, and theme
- Deployment guide rewritten for beginners with Mermaid diagrams
- Live page packet rendering unified into
renderPacketTree()
🧪 Tests
839 tests across 9 suites, 0 failures:
- 30 new migration tests (schema upgrade, idempotency, backup safety, crash recovery)
- 19 new v3 schema tests (ingestion, dedup, view compatibility)
- All pre-existing test suites updated and passing