# 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 ` 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_json` during cold load โ€” per-relay timelines, hop counts, and route stats are intact the moment the server says it's ready. (#1643, 938153dd) - **`/api/nodes` stops 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 `hiddenNamePrefixes` config (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 `TableSort` on 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/nodes` across 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|off` hint 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.homeUrl` override for embedded deployments. (#1576, 9b36b7c4) - Configurable observer-health thresholds. (#1556, 65bd954b) - Detect CDN-fronted deployment + document bypass requirement. (#1564, 63bfa3d9) - Expose `--nav-active-bg` as a themeable token. (#1571, 892eb2c0) ### Performance - Chunked `Load` with 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`: fold `first_seen` into cached map โ€” fixes the #1627 r3 regression. (#1632, 078225a5) - `GOMEMLIMIT` via `runtime.maxMemoryMB` in 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_path` on new observations (regression from #1289). (#1548, 3feb97f1) - Defense-in-depth empty-scope guard in `UpdateNodeDefaultScope`. (#1575, cd19285f) - Skip `default_scope` update when `ScopeName` is 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) - `scanReachRows` DB 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:hover` backgrounds. (#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=20` CI 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`'s `resolved_path` indexing into `loadChunk`. (#1582, 9465949e) - Remove dead server-side backfill flag (stuck `backfilling=true`). (#1583, f7571a26) - Additional follow-up fixes for #1532. (#1580, 373ee816) - Document `writeStatsAtomic` symlink-replace semantics + regression test. (#1588, af669438) ### API - Bypass API limit clamps for internal UI requests (revisit of #1540). (#1589, 7421ead9) - Emit `Cache-Control: no-store` on `/api/*` responses. (#1553, 0c908d2b) ## Performance - Chunked `Load` early-readiness, background index builds, lazy distance index, cached `first_seen`, `GOMEMLIMIT` honored, 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` โ€” sets `GOMEMLIMIT` for 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/nodes` paginates instead of silently truncating at the 500-row cap; internal UI requests bypass the clamp. (#1607, 26105748; #1589, 7421ead9) - `/api/*` responses now emit `Cache-Control: no-store`. (#1553, 0c908d2b) - Per-node Reach available at `GET /api/nodes/{pubkey}/reach`. (#1627, e2212f50) - Hashtag channels from `meshcore-channels` catalogue 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/search` in 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//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//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-store` follow-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" ```