Fixes #2311. Partial follow-up to #1385 (PR-B). Five historical release notes lived outside `docs/release-notes/`. Move v2.6.0, v3.0.0, v3.1.0, v3.4.0 and v3.6.0 into that directory using its existing version naming convention. Release content is preserved except for concise historical notices and four relative-link repairs. The v3.0.0 notice identifies its obsolete Node rollback instructions; current setup links point to README and DEPLOY. No new documentation hierarchy or index is introduced. Validation: all five moves are one-to-one; every document matches its original bytes after only the listed edits; all 14 local Markdown links resolve; no old-path references remain. Existing external links are unchanged. All 218 standalone frontend suites pass. The unchanged backend tree passed full server/ingestor suites, and the unchanged app passed 12 browser smoke cases. All five published documents were rendered in Chromium; historical notices and repaired link targets were verified. Three independent reviews passed. TDD exemption: pure documentation; no application, test, CI, configuration or deployment behavior changed. No settings/customizer implications. [CI passed](https://github.com/Kpa-clawbot/CoreScope/actions/runs/40230718673); its docs-only classification correctly skipped application jobs. The full-suite results above are local validation.
12 KiB
v3.6.0 - The Forensics
Historical release notes. Features and instructions below describe this release. For current setup and deployment, see the README and deployment guide.
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 0xA3 is 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)
nodeBlacklist in 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 + arm64 in a single multi-arch manifest. Raspberry Pi operators: pull and run. No special tags needed.
/nodes Detail Panel + Search (#868)
The nodes detail panel ships with search improvements (#862) - find nodes fast, see their full detail in a slide-out panel.
Deduplicated Top Longest Hops (#848)
Longest hops are now deduplicated by pair with observation count and SNR cues. No more seeing the same link 47 times.
🔥 Performance Wins
StoreTx ResolvedPath Elimination (#806)
The per-transaction ResolvedPath computation is gone - replaced by a membership index with on-demand decode. This was one of the hottest paths in the ingestor.
Node Packet Queries (#803)
Raw JSON text search for node packets replaced with a proper byNode index (#673). Night and day.
Channel Query Performance (#762, #763)
New channel_hash column enables SQL-level channel filtering. No more full-table scan to find messages in a channel.
SQLite Auto-Vacuum (#919, #920)
Incremental auto-vacuum enabled - the database file actually shrinks after retention pruning. No more 2GB database holding 200MB of live data.
Retention-Hours Cutoff on Load (#917)
Load() now applies retentionHours at read time, preventing OOM when the DB has more history than memory allows.
🛡️ Security & Robustness
MQTT Reconnect with Bounded Backoff (#947, #949)
The ingestor now reconnects to MQTT brokers with exponential backoff, observability logging, and bounded retry. No more silent disconnects that kill your data stream.
🐛 Bugs Squashed
This release exterminates 40+ bugs — from protocol-level hash mismatches to pixel-level CSS breakage. Operators told us what hurt; we listened.
- Path inspector "Show on Map" missed origin and first hop (#950) - map view now includes all hops
- Content hash used full header byte (#787) - content hashing now uses payload type bits only, fixing hash collisions between packets that differ only in header flags
- Encrypted channel deep links showed broken UI (#825, #826, #815) - deep links to encrypted channels now show a lock message instead of broken UI when you don't have the key
- Geofilter longitude wrapping (#925) - geofilter builder wraps longitude to [-180, 180]; southern hemisphere polygons no longer invert
- Hash filter bypasses saved region filter (#939) - hash lookups now skip the geo filter as intended
- Companion-as-repeater excluded from path hops (#935, #936) - non-repeater nodes no longer pollute hop resolution
- Customize panel re-renders while typing (#927) - text fields keep focus during config changes
- Per-observation raw_hex (#881, #882) - each observer's hex dump now shows what that observer actually received
- Per-observation children in packet groups (#866, #880) - expanded groups show per-obs data, not cross-observer aggregates
- Full-page obs-switch (#866, #870) - switching observers updates hex, path, and direction correctly
- Packet detail shows wrong observation (#849, #851) - clicking a specific observation opens that observation
- Byte breakdown hop count (#844, #846) - derived from
path_len, not aggregated_parsedPath - Transport-route path_len offset (#852, #853) - correct offset calculation + CSS variable fix
- Packets/hour chart bars + x-axis (#858, #865) - bars render correctly, x-axis labels properly decimated
- Channel timeline capped to top 8 (#860, #864) - no more 47-channel chart spaghetti
- Reachability row opacity removed (#859, #863) - clean rows without misleading gradient
- Sticky table headers on mobile (#861, #867) - restored after regression
- Map popup 'Show Neighbors' on iOS Safari (#840, #841) - link actually works now
- Node detail Recent Packets invisible text (#829, #830) - CSS fix
- /api/packets/{hash} falls back to DB (#827, #831) - when in-memory store misses, DB catches it
- IATA filter bypass for status messages (#694, #802) - status packets no longer filtered out by airport codes
- Desktop node click URL hash (#676, #739) - clicking a node updates the URL for deep linking
- Filter params in URL hash (#682, #740) - all filter state serialized for shareable links
- Hide undecryptable channel messages (#727, #728) - clean default view
- TRACE path_json uses path_sz (#732) - correct field from flags byte, not header hash_size
- Multi-byte adopters (#754, #767) - all node types, role column, advert precedence
- Channel key case sensitivity (#761) - Public decode works correctly
- Transport route field offsets (#766) - correct offsets in field table
- Clock skew sanity checks (#769) - filter epoch-0, cap drift, require minimum samples
- Neighbor graph slider persistence (#776) - default 0.7, persisted to localStorage
- Node detail panel navigation (#779, #785) - Details/Analytics links actually navigate
- Channel key removal (#898) - user-added keys for server-known channels can be removed
- Side-panel Details on desktop (#892) - opens full-screen correctly
- Hex-dump byte ranges client-side (#891) - computed from per-obs raw_hex
- path_json derived from raw_hex at ingest (#886, #887) - single source of truth
- Path pill and byte breakdown hop agreement (#885) - they match now
- Mobile close button + toolbar scroll (#797, #805) - accessible and scrollable
- /health.recentPackets resolved_path fallback (#810, #821) - falls back to longest sibling observation
- Channel filter on Packets page (#812, #816) - UI and API both fixed
- Clock-skew section in side panel (#813, #814) - renders correctly
- Real RSS in /api/stats (#832, #835) - surface actual RSS alongside tracked store bytes
- Hash size detection for transport routes + zero-hop adverts (#747) - correct detection
- Repeater+observer merged map marker (#745) - single marker, not two overlapping
🎨 UI Polish
- QA findings applied across the board (#832, #833, #836, #837, #838) - dozens of small UX fixes from systematic QA pass
📦 Upgrading
git pull
docker compose down
docker compose build prod
docker compose up -d prod
Your existing config.json works as-is. New optional config keys:
nodeBlacklist- array of node hashes to hideobserverRetentionDays- days before stale observers are prunedmemoryBudgetMB- cap on in-memory packet store
Verify
curl -s http://localhost/api/health | jq .version
# "3.6.0"
🙏 External Contributors
- #735 (@efiten) - Serve geofilter builder from app, link from customizer
- #739 (@efiten) - Desktop node click updates URL hash for deep linking
- #740 (@efiten) - Serialize filter params in URL hash for shareable links
- #742 (@Joel-Claw) - Add nodeBlacklist config to hide abusive/troll nodes
- #761 (@copelaje) - Fix channel key case sensitivity for Public decode
- #764 (@Joel-Claw) - Add observer retention - prune stale observers after configurable days
- #802 (@efiten) - Bypass IATA filter for status messages, fill SNR on duplicate observations
- #803 (@efiten) - Replace raw JSON text search with byNode index for node packet queries
- #805 (@efiten) - Mobile close button accessible + toolbar scrollable
- #900 (@efiten) - App-served geofilter docs page
- #917 (@efiten) - Apply retentionHours cutoff in Load() to prevent OOM on cold start
- #924 (@efiten) - Node filter on live page - show only traffic through a specific node
- #925 (@efiten) - Fix geobuilder longitude wrapping for southern hemisphere polygons
- #927 (@efiten) - Skip customize panel re-render while text field has focus
⚠️ Breaking Changes
None. All API endpoints remain backwards-compatible. New fields are additive only.
📊 By the Numbers
| Stat | Count |
|---|---|
| Commits | 134 |
| PRs merged | 105 |
| Lines added | 18,480 |
| Lines removed | 1,632 |
| Files changed | 110 |
| Contributors | 4 |
Previous release: v3.5.2