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