Fixes #1784 ## Summary Step 4 of the #1784 multi-PR project — wires all frontend display consumers to respect the configured `pathTrust.minHashBytesForMapping` value. **Depends on:** #1840 (must be merged first — provides `MC_meetsPathTrust` / `MC_pathBelowTrust` / `MC_getPathTrustThreshold` helpers and `PATH_TRUST` global). ## Changes ### `map.js` — trust-gated route display - `drawPacketRoute` checks `MC_pathBelowTrust` before drawing polylines - When all path hops are below the configured threshold, shows a "Route not displayed" message with config guidance instead of speculative polylines - Cleans up the trust message control when a new route is drawn ### `analytics.js` — subpath trust filtering - `renderTable` in `renderSubpaths` filters route patterns whose hops are below trust threshold - Combined filter logic: both the 1-byte hide toggle AND trust threshold are applied together - Info line shows which filters are active ("1-byte hide + 2-byte trust", etc.) - No-data message explains which filters caused exclusion ### `route-view.js` — speculative path annotation - Path picker groups tagged with `belowTrust` flag when any hop doesn't meet the configured threshold - Speculative paths show `(speculative, <N-byte hops)` annotation with tooltip explaining the trust threshold - Tooltip includes guidance on changing `pathTrust.minHashBytesForMapping` in config.json ### `live.js` — Paths Through widget - `_pathHopsBelowTrust()` helper checks whether all hops in a path are below trust threshold - Widget fallback message distinguishes between "1-byte filtered" (display toggle) and "N-byte trust threshold" (server config) - Message includes the config key for operators to adjust ### `nodes.js` — confidence weight adjustment - `modeWeight` initialization considers the trust threshold - Hash modes below threshold get zero confidence weight - Bucket-0 (legacy/unknown) excluded at threshold >= 2 per #1784 bucket-0 policy ## Testing `test-issue-1633-hide-1byte-hops.js`: - 5 new source-grep guards verifying each consumer references the trust threshold helpers - All 26 tests passing (21 existing + 5 new) ## Files changed (6 files, +136/-6) ``` public/analytics.js | 28 ++++++++++++++++++--- public/live.js | 19 ++++++++++++++- public/map.js | 21 ++++++++++++++++ public/nodes.js | 8 ++++++ public/route-view.js | 16 +++++++++++- test-issue-1633-hide-1byte-hops.js | 50 ++++++++++++++++++++++++++++++ ``` --- **Depends on:** #1840 **Written by:** DeepSeek V4 Pro in Max Mode
14 KiB
Changelog
[Unreleased]
/api/paths/inspectnow marks a candidate asspeculativewhen any hop falls belowpathTrust.minHashBytesForMapping; consumers can distinguish this from score-based speculation throughevidence.perHop[].trusted.
[3.9.1] — 2026-06-12
Patch release on top of v3.9.0 — v3.9.0's container image never published (Playwright flake gated Docker build). See docs/release-notes/v3.9.1.md.
🎨 Accessibility
- WCAG AA contrast pass (#1676,
f0addfda) — two-tier CSS palette; muted-text ≥4.5:1 in both themes; unknown-repeater chip fixed (2.75:1 → 4.95:1). Closes #1671. Partial fix for #1668.
🧪 Test stability
- Slideover E2E flake fix (#1663+followups,
f06359d7) — tightened selectors, bumped data-row wait. Fixes #1662.
[3.9.0] — 2026-06-12
See docs/release-notes/v3.9.0.md for the full notes. 257 commits since v3.8.3 (72 substantive + 185 coverage bumps).
✨ Highlights
- Relay timelines survive an ingestor restart (#1643) — relay-hop attribution is rebuilt from
path_jsonon cold load. - Observer Compare is first-class (#1642, #1645, #1647) — three new entry points + Tufte-grade compare page with state-preserving multi-select.
- Emoji → Phosphor icon migration (#1648, #1649–#1654) — every UI emoji replaced with theme-tinted Phosphor sprites, lint-gated.
- Per-node Reach page + API (#1627) —
GET /api/nodes/{pubkey}/reachwith cache invalidation on blacklist changes (#1636). - Hashtag channels catalogue integration (#1656) — public hashtag channels appear without manual config.
- Operator-customizable name-prefix hiding (#1655) — new
hiddenNamePrefixesconfig (default["🚫"]).
⚙️ Config
- New:
hiddenNamePrefixes,liveMap.maxNodes,runtime.maxMemoryMB, configurable observer-health thresholds,branding.homeUrl, customizer disabled-tabs.
📝 Documentation Corrections (carried from prior [Unreleased])
- PR #1324 historical record correction (#1387) — the merged PR #1324 body referenced four tests that do NOT exist in master:
TestMultibyteCapPersistRoundTrip,TestMultibyteCapPersistSkipsUnknown,TestMaybePersistCoalesces, and aTryLockcoalescing test. The actual tests that landed areTestRunMultibyteCapPersist_AppliesSnapshotandTestRunMultibyteCapPersist_NoSnapshot_NoOp. See issue #1386 for the corrective test additions (round-trip, unknown-key skip, coalescing).
[3.7.2] — 2026-05-06
Hotfix release branched from v3.7.1. Cherry-picks PR #1121 only — no other changes.
🐛 Bug Fixes
- Ingestor: backfill infinite loop on
path_json='[]'rows (#1119, #1121) —BackfillPathJSONAsyncre-selected observations whosepath_jsonwas already'[]', rewrote them to'[]', and looped forever. The migration marker was never recorded and the ingestor sustained 2–3 MB/s WAL writes at idle (~76% CPU insqlite.Exec). Fix: drop'[]'from the WHERE clause so the loop terminates after one full pass and thebackfill_path_json_from_raw_hex_v1marker is written.
[2.5.0] "Digital Rain" — 2026-03-22
✨ Matrix Mode — Full Cyberpunk Map Theme
Toggle Matrix on the live map to transform the entire visualization:
- Green phosphor CRT aesthetic — map tiles are desaturated and re-tinted through a
sepia → hue-rotate(70°) → saturatefilter chain, giving roads, coastlines, and terrain a faint green wireframe look against a dark background - CRT scanline overlay — subtle horizontal lines with a gentle flicker animation across the entire map
- Node markers dim to dark green (#008a22 at 50% opacity) so they don't compete with packet animations
- Forces dark mode while active (saves and restores your previous theme on toggle off)
- Disables heat map automatically (incompatible visual combo)
- All UI panels themed — feed panel, VCR controls, node detail all go green-on-black with monospace font
- New markers created during Matrix mode (e.g. VCR timeline scrub) are automatically tinted
✨ Matrix Hex Flight — Packet Bytes on the Wire
When Matrix mode is enabled, packet animations between nodes show the actual hex bytes from the raw packet data flowing along the path:
- Real packet data — bytes come from the packet's
raw_hexfield, not random/generated - White leading byte with triple-layer green neon glow (
text-shadow: 0 0 8px, 0 0 16px, 0 0 24px) - Trailing bytes fade from bright to dim green, shrinking in size with distance from the head
- Scrolls through all bytes in the packet as it travels each hop
- 60fps animation via
requestAnimationFramewith time-based interpolation (1.1s per hop) - 300ms fade-out after reaching the destination node
- Replaces the standard contrail animation; toggle off to restore normal mode
✨ Matrix Rain — Falling Packet Columns
A separate Rain toggle adds a canvas-rendered overlay of falling hex byte columns, Matrix-style:
- Each incoming packet spawns a column of its actual raw hex bytes falling from the top of the screen
- Fall distance proportional to hop count — 4+ hops reach the bottom of the screen; a 1-hop packet barely drops. Matches the real mesh network: more hops = more propagation = longer rain trail
- Fall duration scales with distance — 5 seconds for a full-screen drop, proportional for shorter
- Multiple observations = more rain — each observation of a packet spawns its own column, staggered 150ms apart. A packet seen by 8 observers creates 8 simultaneous falling columns with ±1 hop variation for visual variety
- Leading byte is bright white with green glow; trailing bytes progressively fade to green
- Entire column fades out in the last 30% of its lifetime
- Canvas-rendered at 60fps — no DOM overhead, handles hundreds of simultaneous drops
- Works independently or with Matrix mode — combine both for the full effect
- Replay support — the ▶ Replay button on packet detail pages now includes raw hex data so replayed packets produce rain
🐛 Bug Fixes
- Fixed null element errors in Matrix hex flight —
getElement()returns null when DivIcon hasn't been rendered to DOM yet during fast VCR replay - Fixed animation null-guard cascade —
pulseNode,animatePath, anddrawAnimatedLinenow bail early if map layers are null (stalesetIntervalcallbacks after page navigation) - Fixed WS broadcast with null packet — deduplicated observations caused
fullPacketto be null in WebSocket broadcasts - Fixed pause button crash — was killing WS handler registration
- Fixed multi-select menu close handler — null-guard for missing elements
⚡ Technical Notes
- Matrix hex flight uses Leaflet
L.divIconmarkers for each character — the smoothness ceiling is Leaflet's DOM repositioning speed. CSS transitions were tested but caused stutter due to conflicts with Leaflet's internal transform updates. - Matrix Rain uses a raw
<canvas>overlay at z-index 9998 for zero-DOM-overhead rendering. Each drop is a simple{x, maxY, duration, bytes, startTime}struct rendered in a singlerequestAnimationFrameloop. - Map tile tinting applies CSS filters to
.leaflet-tile-paneand green overlays via::before/::afterpseudo-elements on the map container (same element as.leaflet-container, so selectors use.matrix-theme.leaflet-containernot descendant.matrix-theme .leaflet-container).
[2.4.1] — 2026-03-22
Hotfix release for regressions introduced in v2.4.0.
Fixed
- Packet ingestion broken:
insert()returned undefined after legacy table removal, causing all MQTT packets to fail silently - Live packet updates not working: pause button
addEventListeneron null element crashedinit(), preventing WS handler registration - Pause button not toggling: event delegation was on
appvariable not in IIFE scope; moved todocument - WS broadcast had null packet data when observation was deduped (2nd+ observer of same packet)
- Multi-select filter menu close handler crashed on null
observerFilterWrap/typeFilterWrapelements - Live map animation cleanup crashed with null
animLayer/pathsLayerafter navigating away (setInterval kept firing)
[2.4.0] — 2026-03-22
UI polish, client-side filtering, time window selector, DB cleanup, and bug fixes.
Added
- Observation-level deeplinks (
#/packets/HASH?obs=OBSERVER_ID) - Observation detail pane (click any child row for its specific data)
- Observation sort: Observer / Path ↑↓ / Time ↑↓ with persistent preference
- Ungrouped mode flattens all observations into individual rows
- Sort help tooltip (ⓘ) explaining each mode
- Distance/Range analytics tab with haversine calculations
- View on Map buttons for distance leaderboard entries
- Realistic packet propagation mode on live map
- Packet propagation time in detail pane
- Replay sends all observations with realistic animation
- Paths-through section on node detail (desktop + mobile)
- Regional filters on all tabs (shared RegionFilter component)
- Favorites filter on live map (packet-level, not node markers)
- Configurable map defaults via
config.json - Hash prefix labels on map with spiral deconfliction + callout lines
- Channel rainbow table (pre-computed keys for common names)
- Zero-API live channel updates via WebSocket
- Channel message dedup by packet hash
- Channel name tags (blue pill) in packet detail column
- Shareable channel URLs (
#/channels/HASH) - API key required for POST endpoints
- HTTPS support (lincomatic PR #105)
- Graceful shutdown (lincomatic PR #109)
- Filter bar: logical grouping, consistent 34px height, help tooltips
- Multi-select Observer and Type filters (checkbox dropdowns, OR logic)
- Hex Paths toggle: show raw hex hash prefixes vs resolved node names
- Time window selector (15min/30min/1h/3h/6h/12h/24h/All) replaces fixed packet count limit
- Pause/resume button (⏸/▶) for live WebSocket updates with buffered packet count
- localStorage persistence for all filter/view preferences
Changed
- Channel keys: plain
String(channelHash),hashChannelsfor auto-derived SHA256 - Node region filtering uses ADVERT-based index (accurate local presence vs mesh-wide routing)
- Header row reflects first sorted observation's data
- Max hop distance filter: 1000km → 300km (LoRa record ~250km)
- Route view labels use deconflicted divIcons
- Channels page hides encrypted messages, shows only decrypted
- Dark mode: active filter buttons retain accent styling
- Region dropdown:
IATA - Friendly Nameformat, proper sizing - Observer/Type filters are pure client-side (no API calls on filter change)
- Packet loading: time-window based (
since) instead of fixed count limit - Header row shows matching observer when observer filter is active
Removed
- Legacy
packetsandpathsdatabase tables (auto-migrated on startup) - Redundant server-side type/observer filtering (client filters in-memory)
Fixed
- Header row showed longest path instead of first observer's path
- Observer/path mismatch when earlier observation arrives later
- Auto-seeding fake data on empty DB (now requires
--seedflag) - Channel "10h ago" bug (used stale
first_seeninstead of current time) - Stale UI: wrong ID type for packet lookup after insert
- ADVERT timestamp validation rejecting valid nodes
- Channels page API spam on every WS update
- Duplicate observations in expanded view
- Analytics RF 500 error (stack overflow with 193K observations)
- Region filter SQL using non-existent column
- Channel hash: decimal→hex, keyed by decrypted name
- Corrupted repeater entries (ADVERT validation at ingestion)
- Hash_size: uses newest ADVERT, precomputed at startup
- Tab backgrounding: skip animations, resume cleanly
- Feed panel position (obscured by VCR bar)
- Hop disambiguation anchored from sender origin
- Packet hash case normalization for deeplinks
- Critical: packet ingestion broken after legacy table removal (
insert()returned undefined) - Sort help tooltip rendering (CSS pseudo-elements don't support newlines)
Performance
/api/analytics/distance: 3s → 630ms/api/analytics/topology: 289ms → 193ms/api/observers: 3s → 130ms/api/nodes: 50ms → 2ms (precomputed hash_size)- Event loop max: 3.2s → 903ms (startup only)
- Pre-warm yields event loop via
setImmediate - Client-side hop resolution
- SQLite manual PASSIVE checkpointing
- Single API call for packet expand (was 3)
[2.3.0] - 2026-03-20
Added
- Packet Deduplication: Normalized storage with
transmissionsandobservationstables — packets seen by multiple observers are stored once with linked observation records - Observation count badges: Packets page shows 👁 badge indicating how many observers saw each transmission
?expand=observations: API query param to include full observation details on packet responsestotalTransmissions/totalObservations: Health and analytics APIs return both deduped and raw counts- Migration script:
scripts/migrate-dedup.jsfor converting existing packet data to normalized schema - Live map deeplinks: Node detail panel links to full node detail, observer detail, and filtered packets
- CI validation:
setup-nodeadded to deploy workflow for JS syntax checking
Changed
- In-memory packet store restructured around transmissions (primary) with observation indexes
- Packets API returns unique transmissions by default (was returning inflated observation rows)
- Home page shows "Transmissions" instead of "Packets" for network stats
- Analytics overview uses transmission counts for throughput metrics
- Node health stats include
totalTransmissionsalongside legacytotalPackets - WebSocket broadcasts include
observation_count
Fixed
- Packet expand showing only the collapsed row instead of individual observations
- Live page "Heard By" showing "undefined pkts" (wrong field name)
- Recent packets deeplink using query param instead of route path
- Migration script handling concurrent dual-write during live deployment
Performance
- 8.19× dedup ratio on production (117K observations → 14K transmissions)
- RAM usage reduced proportionally — store loads transmissions, not inflated observations