Commit Graph

894 Commits

Author SHA1 Message Date
you 7260b36534 fix: live page mobile VCR, LCD aria, feed keyboard (closes #15, #55, #56) 2026-03-19 18:57:21 +00:00
you 72743fd9ee fix: WS debounce helper, clean up remaining window globals (closes #7, #8) 2026-03-19 16:51:34 +00:00
you e1a465b113 fix: live.js — feed overflow, interval leaks, LCD ghost color, VCR aria-labels (closes #1, #2, #3, #11) 2026-03-19 16:47:15 +00:00
you 080d4bc3c1 fix: home.js listener stacking, packets.js filter bar rebuild (closes #4, #6) 2026-03-19 16:46:41 +00:00
you b4d0d6a056 fix: make table rows keyboard-accessible with delegated event listeners (fixes #9)
- 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)
2026-03-19 15:50:18 +00:00
you 5205cf04fe fix: escape decoded.text and decoded.name in innerHTML to prevent XSS (fixes #5)
- 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
2026-03-19 15:46:04 +00:00
you fd77731b54 Fix hash matrix colors: green=free, yellow=taken, red=collision 2026-03-19 09:12:33 +00:00
you 5b78a4a216 Analytics: widen to 1600px, side-by-side card layout
- 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
2026-03-19 09:04:54 +00:00
you 7c90a260ca Fix hash matrix: green=available, blue=1 node, yellow-red=collision 2026-03-19 09:02:38 +00:00
you 8616145c98 Hash matrix: show prefix labels, color by collision risk not traffic
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.
2026-03-19 09:01:08 +00:00
you e1873e1451 Fix route pattern lag: prefix index + disambiguation cache
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+).
2026-03-19 08:58:36 +00:00
you 3f1f6b91c7 Route patterns: use sequential hop disambiguation
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.
2026-03-19 08:54:31 +00:00
you dbb7abb72c Split Hash Collisions into own tab, add hop distances to route patterns
- Hash Stats tab: general hash size distribution, top hops, timeline
- Hash Collisions tab: 16x16 usage matrix + collision risk analysis
- Route Patterns detail: inter-hop distances with color coding
  (green <50km, amber 50-200km, red >200km) and total path distance
2026-03-19 08:48:55 +00:00
you 1c5dfd67a9 Hash matrix: click cell to show nodes using that prefix
Clicking an active cell shows a detail panel with linked node names,
coordinates, and roles. Matrix + detail panel in flex layout.
Hover highlights, selected cell gets accent outline + glow.
2026-03-19 08:47:08 +00:00
you 3831e3e4b9 Add 1-byte hash usage matrix (16x16 heatmap)
Rows = high nibble, columns = low nibble. Color intensity shows
packet count (log scale). Hover for exact count. Placed above
collision risk table in Repeater Hashes tab.
2026-03-19 08:44:45 +00:00
you 169e8b0c02 Rename Hash Sizes → Repeater Hashes, enrich collisions with distance
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)
2026-03-19 08:43:26 +00:00
you 01670e7671 Increase live map node limit from 500 to 2000
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.
2026-03-19 08:38:24 +00:00
you a0c2429756 Drop impossibly distant hops as prefix collisions
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.
2026-03-19 08:34:53 +00:00
you 1159886285 Live map: sequential hop disambiguation matching server logic
Forward + backward pass resolving each ambiguous hop by distance
to previous/next known hop, instead of center-of-path averaging.
2026-03-19 08:29:31 +00:00
you c4e82551c2 Sequential hop disambiguation: resolve each hop by distance to previous
Instead of averaging all hops to a center point, walk the path
sequentially — each ambiguous hop picks the candidate closest to
the previously resolved hop. Forward pass seeds from first known
position, backward pass seeds from observer. This matches physical
reality: packets travel hop-by-hop in RF range.
2026-03-19 08:28:07 +00:00
you 02b8034a4c Fix observer location for hop disambiguation
- ADVERT is payload_type 4 not 1
- Frontend sends observer_id (not just observer_name) since some
  observers have null name
- Observer position derived from geographic center of nodes it
  commonly receives ADVERTs from
- Last hop in path disambiguated by proximity to observer position
2026-03-19 08:26:37 +00:00
you 6e8c941396 Fix route map: bidirectional prefix match for full pubkeys
Packets page now passes server-disambiguated full pubkeys.
Map needs bidirectional prefix match since DB nodes may have
truncated (8-char) or full (64-char) keys. Removes redundant
client-side geographic disambiguation since packets page already
resolved via /api/resolve-hops.
2026-03-19 08:20:19 +00:00
you 81520e4660 Fix hop disambiguation: last hop prefers observer proximity
- Observer name resolved to lat/lon for geographic context
- Last hop in path sorted by distance to observer (not center)
  since it's the node that delivered the packet to the observer
- Packets page now passes observer_name to /api/resolve-hops
- Fixes CroatR1 being picked over Test Repeater for hop 8A
2026-03-19 08:18:26 +00:00
you ec87455b79 Pass server-disambiguated full pubkeys to map route view
Was fetching /api/resolve-hops (with geographic disambiguation) then
throwing away the result and still passing raw 1-byte prefixes. Now
passes full pubkeys so the map doesn't re-disambiguate differently.
2026-03-19 08:15:39 +00:00
you 0eca0ce61c Use longest path sibling for grouped packets
When the same packet is received multiple times (different hop counts
as it propagates), use the reception with the most hops for display.
Fixes routes showing incomplete paths when the first reception had
fewer hops than later ones.

Applies to both grouped query and individual packet detail endpoint.
2026-03-19 08:14:27 +00:00
you 0f06fe881d Clickable route hop markers with popup details + node link
Each hop marker shows: label (Origin/Hop N/Destination), node name,
role, pubkey, coordinates, and 'View Node →' link to node detail.
2026-03-19 08:07:55 +00:00
you 53117c79a7 Geographic prefix disambiguation for route overlay on map page 2026-03-19 08:05:55 +00:00
you 2dabad8fb9 Fix duplicate knownPositions declaration breaking live page 2026-03-19 08:03:59 +00:00
you 4ae565b829 Geographic prefix collision disambiguation on live page
When a 1-byte hop prefix matches multiple nodes, pick the one
geographically closest to the center of other known hops in the
path — same logic as /api/resolve-hops but client-side.

Previously just took the first match, which could be a node in
Chicago matching a local Bay Area hop prefix.
2026-03-19 08:02:07 +00:00
you 4726245988 Fix heatmap remnants: clear nodesLayer and animLayer on scrub
Previous fix only removed markers tracked in nodeMarkers object,
missing glow circles and other elements added directly to nodesLayer.
2026-03-19 07:57:08 +00:00
you fdbd6511ff Scrub replay fetches ALL packets from scrub point to now
No more 5-minute window or 200-packet limit. Fetches up to 10K
packets from the scrub point forward and replays them all.
When replay finishes, resumes live.
2026-03-19 07:56:12 +00:00
you 850768395e Fix: clear nodeActivity+heatmap on scrub; hold playhead after replay ends
- nodeActivity cleared in clearNodeMarkers (removes ghost heatmap)
- When replay finishes, set scrubTs to last packet's timestamp so
  updateTimelinePlayhead holds position instead of snapping right
2026-03-19 07:55:07 +00:00
you 69297d8eec Clear heatmap layer when clearing nodes for replay 2026-03-19 07:51:42 +00:00
you b922c20604 ADVERT packets during replay add nodes to map; simplify unpause
- animatePacket checks for ADVERT type and adds new nodes to map
  with marker if they have location data
- Unpause always resumes to live (removed missed packets prompt)
2026-03-19 07:51:02 +00:00
you 25fc2924e0 Time-travel map: filter nodes by replay timestamp
When scrubbing to a past time, only nodes that had advertised before
that time appear on the map. Nodes that hadn't been seen yet are
hidden.

- Added 'before' param to /api/nodes (filters first_seen <= before)
- loadNodes(beforeTs) accepts optional timestamp filter
- clearNodeMarkers() removes all markers and data
- vcrReplayFromTs clears and reloads nodes for replay time
- vcrResumeLive reloads all nodes (no filter)
2026-03-19 07:50:17 +00:00
you a271696766 Real 7-segment LCD digits, live clock tick, remove confusing counter
- Canvas-based 7-segment digit renderer with ghost segments (dim
  background showing all segments like a real LCD)
- Clock ticks every second in LIVE mode
- Removed packet counter (1/182) from scrub/replay — only shows
  +N PKTS when paused with missed packets
2026-03-19 07:45:17 +00:00
you bcf1a6d90a Add vintage LCD panel to VCR bar
Dark green-on-black LCD display on right side of VCR bar showing:
- Mode: LIVE / PAUSE / PLAY 2x (green)
- Time: HH:MM:SS with glow (green)
- Packet counter: +N PKTS when paused, N/total during replay (amber)

Styled with dark background, inset shadow, monospace font,
text-shadow glow — vintage VCR aesthetic.
2026-03-19 07:41:57 +00:00
you 49ed4b80e2 Fix scrub replay: add 'until' filter to API, fetch correct time window
ROOT CAUSE: API query used ORDER BY timestamp DESC with no upper
bound — scrubbing to 3h ago fetched the newest 200 packets (from
now), not packets near the scrub target.

Fix:
- Added 'until' query param to /api/packets (both grouped and flat)
- vcrReplayFromTs fetches a 5-minute window around the scrub target
- Server restart required (old process was stale on port 3000)
2026-03-19 07:35:05 +00:00
you c969a0218f Fix scrub: auto-replay on release, replay from correct timestamp
1. scrubRelease now calls vcrReplayFromTs directly (no pause step)
2. vcrReplayFromTs builds replay buffer from ONLY fetched DB packets,
   not merged with stale WS buffer entries. Starts playhead at 0
   (first fetched packet), not closest-match in mixed buffer.
   Fixes replay starting from session start (00:06) instead of
   scrub target.
2026-03-19 07:25:32 +00:00
you 4a21f8419f Fix replay after scrub: fetch from DB and play from scrub point
Scrub + release now pauses (no fetch). Hitting play detects
VCR.scrubTs and fetches packets from DB around that timestamp,
then replays 50 packets from the closest match.
2026-03-19 07:22:51 +00:00
you b7d8ec0cad Rewrite scrubber: simple pause on release, no async fetch
Previous approach tried to fetch packets from DB on scrub release,
causing race conditions, rubber-banding, and stale state. Replaced
with dead-simple approach:
- Drag: moves playhead visually + updates clock
- Release: pauses at that position (VCR.scrubTs holds timestamp)
- No async fetch, no replay on release
- updateTimelinePlayhead uses scrubTs to hold position
- Click LIVE to resume
2026-03-19 07:21:00 +00:00
you 0c1e50499b Add VCR digital clock display
Red monospace clock next to LIVE/mode indicator shows current
playhead time. Updates during drag, replay ticks, and on mode
changes. Retro VCR aesthetic with text-shadow glow.
2026-03-19 07:18:14 +00:00
you 932ac4807e Fix scrubber snap-back on release: keep dragging flag during fetch
VCR.dragging=false was set on mouseup before the async DB fetch
completed. During the fetch (~100-200ms), updateTimelinePlayhead
saw dragging=false, playhead=-1, fell to else→x=cw (right snap).

Fix: keep dragging=true until fetch resolves and replay starts.
2026-03-19 07:16:10 +00:00
you 74013c17a2 Fix VCR scrubber: remove CSS transition causing drag lag
The playhead had 'transition: left 0.3s ease' which made it animate
300ms behind the mouse during drag (felt stuck) and ease to an offset
position on release (felt like rubber-banding). Removed.
2026-03-19 07:14:30 +00:00
you b22a9bccca Fix VCR scrubber: freeze timeline reference during replay
THE root cause: timeline coordinate system uses Date.now() as right
edge, making it a sliding window. Playhead position = (packetTs -
(now - scope)) / scope — but 'now' advances every frame, sliding
all positions left continuously. Any scrub position drifts.

Fix: VCR.frozenNow captures Date.now() when leaving LIVE mode.
All timeline calculations use frozenNow instead of Date.now() during
REPLAY/PAUSED. Timeline stops sliding. Playhead stays put.
Cleared on return to LIVE.
2026-03-19 07:10:40 +00:00
you aa7445e1fb Fix VCR scrubber rubber-band: hold dragPct during async fetch
Root cause traced: mouseup sets VCR.dragging=false and starts async
fetch. Between fetch start and response (~50-200ms), the 30s interval
fires updateTimelinePlayhead() which found no matching branch for
REPLAY+old playhead, defaulting to x=cw (right edge snap).

Fix: dragPct now takes priority over buffer-based position during
REPLAY/PAUSED modes. Cleared only when fetch completes and replay
actually starts with real buffer data.
2026-03-19 07:07:26 +00:00
you f608f55c3e Fix Recent Activity on nodes page: remove extra closing div
An extra </div> was closing the node-detail wrapper before the
Recent Activity section rendered, causing content to fall outside
the styled container.
2026-03-19 07:06:01 +00:00
you 4d1f3ce09d Fix VCR scrubber: limit replay to 50 packets from scrub point
Root cause: scrub replay was playing through the ENTIRE buffer
from scrub point to end (thousands of packets), racing the playhead
forward to now. Now caps replay at 50 packets from scrub point,
then pauses. Hit LIVE to resume real-time.
2026-03-19 07:02:42 +00:00
you 338054d759 Fix VCR scrubber: pause after replay ends, hold playhead position
Two root causes fixed:
1. startReplay() was calling vcrResumeLive() when buffer exhausted,
   snapping back to LIVE mode. Now pauses instead.
2. updateTimelinePlayhead() recalculated position from timestamps
   relative to now (which shifts). Now holds at dragPct position
   when paused after a scrub.
2026-03-19 07:01:01 +00:00
you ecbfe4246b Fix VCR scrubber: pure visual drag + DB fetch on release
Root cause: scrubbing moved playhead to closest buffer entry (recent
WS packets only), then replay tick() recalculated position relative
to current time, snapping it back. Fixed by splitting into two phases:
1. scrubVisual: only moves the DOM playhead element during drag
2. scrubCommit: on release, fetches packets from DB around target
   timestamp, merges into buffer, seeks to closest entry, starts replay.
No more rubber-banding.
2026-03-19 06:58:42 +00:00