Add touchmove/touchend handlers to show the time tooltip during touch
scrubbing on the timeline, mirroring the existing mousemove behavior.
closes#19closes#27closes#54closes#57closes#58closes#59closes#60closes#61closes#62closes#63closes#64
Additional fixes in this commit:
- #27: Add drag resize handle on feed panel right edge, persist width to localStorage
- #54: Add aria-describedby to heat/ghost toggles with sr-only descriptions
- #57: Refactor legend to use semantic ul/li with descriptive text, h3 headings
- #58: Wrap scope buttons in role=radiogroup, add role=radio and aria-checked
- #59: Add role=alertdialog to VCR prompt, auto-focus first button on show
- #60: Add legend toggle button visible on mobile to show/hide legend overlay
- #61: Position feed detail card as full-width bottom sheet on mobile
- #62: Add pin button to nav bar to prevent auto-hide
- #63: Adjust VCR.playhead when buffer is spliced to prevent stale indices
- #64: Standardize fetch limits to 2000 for both vcrRewind and vcrReplayFromTs
- Map controls panel collapsible with toggle button, default collapsed on mobile (closes#16)
- jumpToRegion filters nodes by region observers instead of fitting all (closes#33)
- Detail panel Leaflet maps tracked and removed on re-selection (closes#34)
- Popup HTML uses semantic dl/dt/dd instead of table (closes#46)
- fitBounds now tracks user interaction instead of savedView const (closes#47)
- esc() replaced with safeEsc fallback for implicit global safety (closes#48)
- Checkboxes in map controls have explicit for/id association (closes#49)
- Sort columns have aria-sort attributes (closes#50)
- Filter selects have aria-label attributes (closes#51)
- Back button uses addEventListener instead of inline onclick (closes#52)
- Detail panel map height increased to 280px on desktop (closes#53)
- Replace inline onclick on <tr> elements with data-action/data-value attributes
- Add tabindex="0" and role="row" to all clickable rows
- Add delegated click and keydown (Enter/Space) listeners on containers
- Remove window._pktSelect, _pktToggleGroup, _pktSelectHash, _nodeSelect globals
- Convert to local functions referenced by delegated handlers
Affected files: packets.js, nodes.js, analytics.js
(channels.js and observers.js had no interactive <tr> elements)
- nodes.js: escape decoded.text and decoded.name in advertisement list rendering
- packets.js: escape decoded.text in summary display and decoded.name in fieldRow
- max-width 900px → 1600px for analytics page
- Added analytics-grid CSS class for auto-fit columns
- Hash Stats: distribution + timeline side-by-side, adopters + top hops side-by-side
- RF, Topology, Channels already used analytics-row flex layout
- Cards now fill available screen width instead of being squished
Each cell displays the hex prefix (AB, C3, etc). Color meaning:
- Dark: unused prefix (no known nodes)
- Green: 1 node using it (safe, no collision)
- Yellow→Red: 2+ nodes sharing prefix (collision risk)
Legend added below matrix.
Built O(1) prefix index on allNodes (replaces O(n) filter per hop).
Cache disambiguation results by hop sequence key — same path only
resolved once. Subpaths endpoint: ~1s for 19K packets (was 30s+).
Replace naive first-match resolution in /api/analytics/subpaths and
/api/analytics/subpath-detail with shared disambiguateHops() function.
Each path is now resolved with forward+backward nearest-neighbor pass
and distance sanity check, matching live map and resolve-hops logic.
1-byte collision table now shows:
- Max pairwise distance between colliding nodes
- Classification: Local (<50km, true collision), Regional (50-200km,
possible atmospheric), Distant (>200km, internet bridge/MQTT/separate mesh)
- Coordinates for each node
- Legend explaining each classification
- Sorted: distant first (most interesting)
500 limit was cutting off nodes, causing single-candidate false
matches (e.g. Osprey-Base in Seattle winning for prefix 60 when
little russia in SF was beyond the limit). With ~400 nodes having
coords, 2000 ensures all are loaded.
After sequential disambiguation, sanity-check each hop: if it's
>200km (~1.8°) from both neighbors, it's almost certainly a 1-byte
prefix collision with a distant node, not an actual hop. Mark as
unreliable (server) or drop from animation (live map).
Fixes packet 19384 bouncing to Seattle — Spiden Repeater shares
prefix 1D with an unknown local node.