Perf page now accessible from main nav (⚡ Perf).
Shows in-memory packet store metrics: packets in RAM, memory used/limit,
queries served, live inserts, evictions, index sizes.
node benchmark.js [--runs N] [--json]
Adds ?nocache=1 query param to bypass server cache for benchmarking.
Tests all 21 endpoints cached vs cold, shows speedup comparison.
Existing groups get count/observer_count incremented, latest timestamp
updated, longest path kept. New hashes get a new group prepended.
Expanded children updated inline. No more /api/packets re-fetch on
any incoming packet in either mode.
Non-grouped mode: new packets from WebSocket are filtered client-side
and prepended to the table, no API call. Grouped mode still re-fetches
(group counts change). Server broadcast now includes full packet row.
Eliminates repeated /api/packets fetches on every incoming packet.
4 parallel subpath queries were each scanning 27K packets independently
(937ms + 1.99s + 3.09s + 6.19s). Now one shared computation builds all
subpath data, cached for 1hr. Subsequent queries just slice the result.
Pre-warmed on startup so first user never sees a cold call.
Zero SQLite reads from packets table. Every endpoint that previously
scanned packets now reads from the in-memory PacketStore.
Expected: subpaths from 1.6s to <100ms, topology from 700ms to <50ms,
RF from 270ms to <30ms on cold calls.
- PacketStore loads all packets into memory on startup (~11MB for 27K packets)
- Indexed by id, hash, observer, and node pubkey for fast lookups
- /api/packets, /api/packets/timestamps, /api/packets/:id all served from RAM
- MQTT ingest writes to both RAM + SQLite
- Configurable maxMemoryMB (default 1024MB) in config.json packetStore section
- groupByHash queries computed in-memory
- Packet store stats exposed in /api/perf
- Expected: /api/packets goes from 77ms to <1ms
All cache TTLs now read from config.json cacheTTL section (seconds).
Client fetches config on load via GET /api/config/cache.
config.example.json updated with defaults.
Edit config.json, restart server — no code changes needed to tweak TTLs.
- Mobile (≤640px) default columns: time, hash, type, details (hide region, observer, path, rpt, size)
- Detail panel hidden on mobile; tapping row opens slide-up bottom sheet (70vh max, close button, drag handle)
- Filter bar collapses to single 'Filters' toggle button on mobile
- Table font 11px, tighter padding, no min-width forcing horizontal scroll
- Panel-right completely hidden on mobile (no split layout)
Live page (priority):
- Fix feed panel max-height from 180px to 40vh (was clipping content)
- Fix VCR bar: bump breakpoint from 600px to 640px, hide LCD, ensure
no wrapping, increase touch targets to 44px min
- Remove rogue ≤600px rules that hid feed/legend/toggle entirely
- Fix legend toggle: was using inverted logic (legend-mobile-hidden
toggled off instead of legend-mobile-visible toggled on)
- Header: constrain width to viewport, reduce padding/font on mobile
- Feed detail card: add max-height 50vh + overflow-y auto to prevent
clipping off screen
- Disable feed resize handle on mobile (not practical for touch)
- Ensure Leaflet zoom controls clear mobile header
Packets page:
- panel-left gets min-height 50vh + overflow-x with -webkit touch scrolling
- data-table gets min-width 500px so it scrolls horizontally instead of
collapsing columns to nothing
- panel-right removes max-height 50vh cap (was hiding detail panel)
- Filter bar buttons get 44px min touch target
- Node filter wrap goes full width
Nodes page:
- Node count pills wrap properly
- Smaller font on count pills for narrow screens
Analytics pages:
- analytics-grid goes single column on mobile
- analytics-page reduces padding
Style fixes (global):
- Filter bar gap reduced to 4px on mobile
- All cache busters updated