11 KiB
CoreScope v3.9.0
Upgrade urgency: Medium — fixes the post-restart "relay timelines empty" regression, surfaces silent /api/nodes truncation, 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.homeUrloverride for embedded deployments. (#1576,9b36b7c4)- Configurable observer-health thresholds. (#1556,
65bd954b) - Detect CDN-fronted deployment + document bypass requirement. (#1564,
63bfa3d9) - Expose
--nav-active-bgas a themeable token. (#1571,892eb2c0)
Performance
- Chunked
Loadwith early HTTP readiness — server accepts requests while heavy load completes in the background. (#1596,bc1822e4) - Background subpath + pathHop index builds with ready gates. (#1604,
df61660a) - Lazy distance-index build on first request. (#1597,
5629a489) neighbor_api: foldfirst_seeninto cached map — fixes the #1627 r3 regression. (#1632,078225a5)GOMEMLIMITviaruntime.maxMemoryMBin server + ingestor. (#1595,1b112f0b)- SQLite writer-lock wait/hold instrumentation per component. (#1594,
222bfdf6)
Bug Fixes
Ingestor
- Subscribe to MQTT before startup maintenance; buffer until the writer is free. (#1609,
18810b5c) - Decode firmware 1.16.0 extended ACK (5/6-byte payloads). (#1618,
9612f08e) - Write
resolved_pathon new observations (regression from #1289). (#1548,3feb97f1) - Defense-in-depth empty-scope guard in
UpdateNodeDefaultScope. (#1575,cd19285f) - Skip
default_scopeupdate whenScopeNameis empty. (#1569,05af6c6e) - Address #1609 follow-up findings — config doc, receipt-time liveness, buffer stop/clamp warn. (#1623,
3d122665)
Reach
- Bust response cache on blacklist change. (#1636,
8295c211) scanReachRowsDB errors must surface as 500, not 404. (#1635,43be1bb7)- Narrow-viewport CSS — no horizontal scroll, map no longer shrunken. (#1634,
59d66469)
Frontend / UI
- Render analytics Channels group-header sprites as HTML, not escaped text. (#1658,
fb6bb085) - Bump feed-detail-card z-index + make popup draggable. (#1620,
f66ff40a) - Theme-track
.vcr-scope-btn.active+.copy-link-btn:hoverbackgrounds. (#1578,16c7ea4b) - Replay handoff no longer freezes the map (suppressLive flag). (#1603,
1f65d781) - Detach slide-over panel on close — architectural focus-restore fix +
--repeat-each=20CI gate. (#1617,37a7a927) getTileUrl()now invokes function-typed provider URLs + regression tests. (#1615,dc433e41)- Reliably restore row focus on panel close. (#1602,
1be0aec8) - Gesture hints — edge-drawer mobile-only + row-swipe widening (re-fix). (#1586,
116efe4b) - Honor time-window filter on Route Patterns analytics. (#1592,
d6384c3c) - Live corner-cycle button clears drag state. (#1568,
2b45f787) - Observers aggregate header shows "Last updated" timestamp. (#1563,
a7ad2be1) - Mirror
Load'sresolved_pathindexing intoloadChunk. (#1582,9465949e) - Remove dead server-side backfill flag (stuck
backfilling=true). (#1583,f7571a26) - Additional follow-up fixes for #1532. (#1580,
373ee816) - Document
writeStatsAtomicsymlink-replace semantics + regression test. (#1588,af669438)
API
- Bypass API limit clamps for internal UI requests (revisit of #1540). (#1589,
7421ead9) - Emit
Cache-Control: no-storeon/api/*responses. (#1553,0c908d2b)
Performance
- Chunked
Loadearly-readiness, background index builds, lazy distance index, cachedfirst_seen,GOMEMLIMIThonored, writer-lock instrumentation — see "Performance" under What's New.
Security
- Detect CDN-fronted deployment and document the bypass requirement so rate limits and PoW gates can't be silently routed around. (#1564,
63bfa3d9)
Breaking changes
None.
Operator-facing changes / config
New config (config.example.json, all opt-in, safe defaults):
hiddenNamePrefixes: ["🚫"]— node-name prefixes that hide a node from/api/nodes,/api/nodes/search,/api/nodes/{pubkey}. DB rows preserved; analytics history intact. Mirrors the convention used by other MeshCore map dashboards. Opt out with[]. (#1655,825b2648)liveMap.maxNodes: 2000— cap nodes rendered on the live map; raise for big meshes, lower for low-power dashboards. (#1577,1bdb92de)runtime.maxMemoryMB— setsGOMEMLIMITfor server + ingestor; tune against container limits. (#1595,1b112f0b)- Observer-health thresholds — previously hard-coded, now configurable. (#1556,
65bd954b) branding.homeUrl— override the "Home" link target for embedded deployments. (#1576,9b36b7c4)- Customizer disabled-tabs — config-driven hide list. (#1579,
7292d60f)
Behavior changes:
/api/nodespaginates instead of silently truncating at the 500-row cap; internal UI requests bypass the clamp. (#1607, 26105748; #1589,7421ead9)/api/*responses now emitCache-Control: no-store. (#1553,0c908d2b)- Per-node Reach available at
GET /api/nodes/{pubkey}/reach. (#1627,e2212f50) - Hashtag channels from
meshcore-channelscatalogue appear in the channels list without manual config. (#1656,e04c7113)
Behind the scenes
- Emoji → Phosphor migration (six PRs). M1 top-nav/mobile-nav/Compare (#1649,
55e4d957) · M2 page headers + table chrome (#1650,30627454) · M3 detail panes + badges (#1651,b812a98a) · M4 map + route overlays (#1652,2b6809cd) · M5 settings + customize (#1653,1116801b) · M6 final sweep + lint gate + carry-forwards (#1654,89eade6e). Tracking: #1648. - CI: bump go test timeout 10m → 15m (suite grew past 10m post-#1655). (#1661,
0712c5ff) - Test: tighten slideover row selector to avoid the virtual-scroll spacer race. (#1663,
037dc8c4) - Test: subpaths_window tests wait for index readiness after the #1595 chunked load. (#1621,
ad41b9bb) - Test: mock
/api/nodes/searchin home-coverage E2E (closes #1313). (#1584,6a027b03) - Refactor: extract pure helpers into
route-view-utils.js. (#1581,545013d3)
Verification
Test plan: workspace-meshcore/test-plans/v3.9.0-cdp-test-plan.md (93 tests across 16 sections)
Initial run (master pre-#1665, 2026-06-12 00:45 UTC): 56 pass / 22 partial / 5 fail / 14 skipped. Two BLOCKER lint-gate breaches surfaced — .obs-clock-naive-chip (#v384-1.2, 14× ⚠️) and analytics Channels encrypted labels (#v384-12.18, 158× 🔒) — plus one API contract regression (/api/nodes/<bad>/reach returns 404, expected 500/200). 22 partials were plan selector drift (provider names, panel selectors) not code regressions.
Final run (post-#1665, 2026-06-12 01:35 UTC): v384-1.2 ✅ (11 chips, 11 sprites, 0 emoji). v384-12.18 ✅ (315 lock sprites, 0 🔒 emoji).
Known partials carried forward (recoverable, not blockers): plan selector drift in §§5, 8, 9, 12 — plan to be tightened in v3.8.5 cycle.
Open follow-up issues: #1659 (analytics warm-up data), #1660 (UI loading banner). Both are UX improvements, not regressions.
External API regression to investigate post-release: /api/nodes/<unknown-pubkey>/reach returns 404 instead of 500/200-empty per #1631 contract. Doc/code mismatch, low severity. (To file as issue.)
Acknowledgements
External contributors made this release:
- @efiten — relay-attribution rebuild on cold-load (#1643), paginate
/api/nodes(#1637), per-node Reach page (#1627), MQTT subscribe-before-maintenance (#1609), remove dead backfill flag (#1583), plus #1625/#1626 (per-node Reach relanding). - @EldoonNemar — OSM / Stamen tile provider support (#1533),
Cache-Control: no-storefollow-up (#1580), internal-bypass for API limit clamps (#1589), reliable row-focus restoration on panel close (#1602).
Tagging
git tag -a v3.9.0 e74e8607 -m "v3.9.0"