Commit Graph

773 Commits

Author SHA1 Message Date
you 45fd9546ff Fix: revert WS handler to sync — async resolve was blocking rendering
Per-observer resolve in the WS handler made it async, which
broke the debounce callback (unhandled promise + race conditions).
Live packets now render immediately with global cache. Per-observer
resolution happens on initial load and packet detail only.
2026-03-23 00:16:44 +00:00
you e333e5317d Fix: per-observer hop resolution for WS live packets too
New packets arriving via WebSocket were only getting global
resolution. Now ambiguous hops in WS batches also get per-observer
server-side resolution before rendering.
2026-03-23 00:11:01 +00:00
you 7da6860fdb Fix: per-observer hop resolution for packet list
Ambiguous hops in the list now get resolved per-observer via
batch server API calls. Cache uses observer-scoped keys
(hop:observerId) so the same 1-byte prefix shows different
names depending on which observer saw the packet.

Flow: global resolve first (fast, covers unambiguous hops),
then batch per-observer resolve for ambiguous ones only.
2026-03-23 00:08:41 +00:00
you 600fe7972e Fix: resolve sender GPS from DB for channel texts and all packet types
When packet doesn't have lat/lon directly (channel messages, DMs),
look up sender node from DB by pubkey or name. Use that GPS as
the origin anchor for hop disambiguation. We've seen ADVERTs from
these senders — use that known location.
2026-03-23 00:05:27 +00:00
you eeeefb981b Fix: don't apply time window when navigating to specific packet hash
Direct links like #/packets/HASH should always show the packet
regardless of the time window filter.
2026-03-23 00:04:03 +00:00
you d82b08def7 Fix: sort regional candidates by distance to IATA center
Without sender GPS (channel texts etc), the forward pass had no
anchor and just took candidates[0] — random order. Now regional
candidates are sorted by distKm to observer IATA center before
disambiguation. Closest to region center = default pick.
2026-03-22 23:59:33 +00:00
you c96bfe3b2c Fix: packet detail uses server-side resolve-hops with GPS anchor
Client-side HopResolver wasn't properly disambiguating despite
correct data. Switched detail view to use the server API directly:
/api/resolve-hops?hops=...&observer=...&originLat=...&originLon=...

Server-side resolution is battle-tested and handles regional
filtering + GPS-anchored disambiguation correctly.
2026-03-22 23:52:44 +00:00
you bad7819cac Debug: log renderDetail GPS anchor and resolved hops 2026-03-22 23:50:07 +00:00
you b0c114eb2c Fix: detail view always re-resolves hops fresh with sender GPS
List view resolves hops without anchor (no per-packet context).
Detail view now always re-resolves with the packet's actual GPS
coordinates + observer, overwriting stale cache entries.
Removed debug logging.
2026-03-22 23:47:34 +00:00
you 779abdfa71 Debug: trace renderDetail re-resolve inputs 2026-03-22 23:46:47 +00:00
you e3b0fa3162 Debug: log HopResolver.resolve inputs to trace disambiguation 2026-03-22 23:44:53 +00:00
you 6fa5249656 Fix: pass sender lat/lon as origin anchor for hop disambiguation
ADVERT packets have GPS coordinates — use them as the forward
pass anchor so the first hop resolves to the nearest candidate
to the sender, not random pick order.
2026-03-22 23:35:10 +00:00
you 3a7fb77552 Packet detail: re-resolve hops with observer for regional conflicts
The general hop cache was populated without observer context,
so all conflicts showed filterMethod=none. Now renderDetail()
re-resolves hops with pkt.observer_id, getting proper regional
filtering with distances and conflict flags.
2026-03-22 23:29:46 +00:00
you bd2c978bba Conflict badge: bigger clickable button with popover pane
⚠3 is now a yellow button (not tiny superscript). Clicking it
opens a popover listing all regional candidates with:
- Node name (clickable → node detail page)
- Distance from observer region center
- Truncated pubkey

Popover dismisses on outside click. Each candidate is a link
to #/nodes/PUBKEY for full details.
2026-03-22 23:25:32 +00:00
you 4631c7688e Packet detail byte breakdown uses shared HopDisplay for path hops
Replaces inline conflict rendering with HopDisplay.renderHop() —
consistent regional-only tooltips everywhere.
2026-03-22 23:24:02 +00:00
you 398dccad8f Conflict tooltips show only regional candidates, ignore global noise 2026-03-22 23:22:16 +00:00
you 8d7ba43265 Hex Paths mode still shows conflict tooltips
hexMode flag shows raw hex prefix as display text but keeps
the full conflict tooltip, link, and warning badges.
2026-03-22 23:20:28 +00:00
you 6e0151b7e1 Shared HopDisplay module for consistent conflict tooltips
New hop-display.js: shared renderHop() and renderPath() with
full conflict info — candidate count, regional/global flags,
distance, filter method. Tooltip shows all candidates with
details on hover.

packets.js: uses HopDisplay.renderHop() (was inline)
nodes.js: path rendering uses HopDisplay when available
style.css: .hop-current for highlighting the viewed node in paths

Consistent conflict display across packets + node detail pages.
2026-03-22 23:12:11 +00:00
you 396d875044 Fix: hoist targetNodeKey to module scope so loadNodes can access it 2026-03-22 23:05:56 +00:00
you 412e584133 Fix: delay popup open after setView, add debug logging
Leaflet needs map to settle after setView before popup can open.
Added 500ms delay + console.warn if target marker not found.
2026-03-22 23:01:18 +00:00
you 91e6f0eb08 Packet detail: resolve location for channel texts + all node types
For packets without direct lat/lon (GRP_TXT, TXT_MSG):
- Look up sender by pubKey via /api/nodes/:key
- Look up sender by name via /api/nodes/search?q=name
- Show location + 📍map link when node has coordinates

Works for decrypted channel messages (sender field), direct
messages (srcPubKey), and any packet type with a resolvable sender.
2026-03-22 23:00:09 +00:00
you 50d6a7b068 Fix: map node highlight uses _nodeKey instead of alt text matching
Store public_key on each Leaflet marker as _nodeKey. Match by
exact pubkey instead of fragile alt text substring search.
2026-03-22 22:55:46 +00:00
you f6b3676b65 Map: navigate to node by pubkey from packet detail
📍map link now uses #/map?node=PUBKEY. Map centers on the node
at zoom 14 and opens its popup. No fake markers — uses the
existing node marker already on the map.
2026-03-22 22:51:15 +00:00
you 333d1cf7eb Map: highlight pin when navigated from packet detail
Red circle marker with tooltip at the target coordinates,
fades out after 10 seconds. Makes it obvious where the
packet location is on the map.
2026-03-22 22:47:01 +00:00
you 115c65bea4 Fix: observer detail packet links use hash, not ID
Was linking to #/packet/ID (wrong route + observation ID).
Now links to #/packets/HASH (correct route + packet hash).
2026-03-22 22:44:00 +00:00
you edab731b47 Location link points to our own map, not Google Maps
Packet detail 📍map link now navigates to #/map?lat=X&lon=Y&zoom=12.
Map page reads lat/lon/zoom from URL query params to center on
the linked location.
2026-03-22 22:43:29 +00:00
you ced727c96d Fix: packet-detail page loads observers before rendering
Direct navigation to #/packet/ID skipped loadObservers(), so
obsName() fell through to raw hex pubkey. Now loads observers
first.
2026-03-22 22:42:38 +00:00
you a89a925e4c Packet detail: show location for ADVERTs and nodes with lat/lon
Shows coordinates with Google Maps link for packets that have
lat/lon in decoded payload (ADVERTs, known nodes). Includes
node name when available.
2026-03-22 22:41:35 +00:00
you b7573df53e Fix: move /api/iata-coords route after app initialization
Route was at line 16 before express app was created — caused
'Cannot access app before initialization' crash on startup.
2026-03-22 22:26:15 +00:00
you bd43936e8b Show observer IATA regions in packet, node, and live detail views
- packets.js: obsName() now shows IATA code next to observer name, e.g. 'EW-SFC-DR01 (SFO)'
- packets.js: hop conflicts in field table show distance (e.g. '37km')
- nodes.js: both full and sidebar detail views show 'Regions: SJC, OAK, SFO' badges and per-observer IATA
- live.js: node detail panel shows regions in 'Heard By' heading and per-observer IATA
- server.js: /api/nodes/:pubkey/health now returns iata field for each observer
- Bump cache busters
2026-03-22 22:21:01 +00:00
you f25e96036d Client-side regional hop filtering (#117)
HopResolver now mirrors server-side layered regional filtering:
- init() accepts observers list + IATA coords
- resolve() accepts observerId, looks up IATA, filters candidates
  by haversine distance (300km radius) to IATA center
- Candidates include regional, filterMethod, distKm fields
- Packet detail view passes observer_id to resolve()

New endpoint: GET /api/iata-coords returns airport coordinates
for client-side use.

Fixes: conflict badges showing "0 conflicts" in packet detail
because client-side resolver had no regional filtering.
2026-03-22 22:18:01 +00:00
you 90881f0676 Regional hop filtering: layered geo + observer approach (#117)
Layer 1 (GPS, bridge-proof): Nodes with lat/lon are checked via
haversine distance to the observer IATA center. Only nodes within
300km are considered regional. Bridged WA nodes appearing in SJC
MQTT feeds are correctly rejected because their GPS coords are
1100km+ from SJC.

Layer 2 (observer-based, fallback): Nodes without GPS fall back to
_advertByObserver index — were they seen by a regional observer?
Less precise but still useful for nodes that never sent ADVERTs
with coordinates.

Layer 3: Global fallback, flagged.

New module: iata-coords.js with 60+ IATA airport coordinates +
haversine distance function.

API response now includes filterMethod (geo/observer/none) and
distKm per conflict candidate.

Tests: 22 unit tests (haversine, boundaries, cross-regional
collision sim, layered fallback, bridge rejection).
2026-03-22 22:09:43 +00:00
you 2fc07ef697 Fix #117: Regional filtering for repeater ID resolution
1-byte (and 2-byte) hop IDs match many nodes globally. Previously
resolve-hops picked candidates from anywhere, causing cross-regional
false paths (e.g. Eugene packet showing Vancouver repeaters).

Fix: Use observer IATA to determine packet region. Filter candidates
to nodes seen by observers in the same IATA region via the existing
_advertByObserver index. Fall back to global only if zero regional
candidates exist (flagged as globalFallback).

API changes to /api/resolve-hops response:
- conflicts[]: all candidates with regional flag per hop
- totalGlobal/totalRegional: candidate counts
- globalFallback: true when no regional candidates found
- region: packet IATA region in top-level response

UI changes:
- Conflict count badge (⚠3) instead of bare ⚠
- Tooltip shows regional vs global candidates
- Unreliable hops shown with strikethrough + opacity
- Global fallback hops shown with red dashed underline
2026-03-22 21:38:24 +00:00
you f4e6c34ad5 feat: add built-in IATA-to-city mapping for region dropdown (#116)
Add window.IATA_CITIES with ~150 common airport codes covering US, Canada,
Europe, Asia, Oceania, South America, and Africa. The region filter now
falls back to this mapping when no user-configured label exists, so region
dropdowns show friendly city names out of the box.

Closes #116
2026-03-22 21:22:23 +00:00
you 5b21923641 fix: region dropdown layout for longer city name labels (#116)
- Add width: max-content to dropdown menu for auto-sizing
- Add overflow ellipsis + max-width on dropdown items for very long labels
- Checkboxes already flex-shrink: 0, no text wrapping with white-space: nowrap
2026-03-22 21:21:39 +00:00
you c35d10ace7 Audio Lab: click any note row to play it individually
Each note in the sequence table has a ▶ button and the whole row
is clickable. Plays a single oscillator with the correct envelope,
filter, and frequency for that note. Highlights the corresponding
hex byte, table row, and byte bar while it plays.

Also added MeshAudio.getContext() accessor for audio lab to create
individual notes without duplicating AudioContext.
2026-03-22 19:41:00 +00:00
you 81899b1e80 Audio Lab: fix highlight timing vs speed setting
computeMapping was applying speedMult on top of BPM that already
included it (setBPM(baseBPM * speedMult)). Double-multiplication
made highlights run at wrong speed. BPM already encodes speed.
2026-03-22 19:39:18 +00:00
you e2210f6d2b Audio Lab: show WHY each parameter has its value
Sound Mapping: 3-column table (Parameter | Value | Formula/Reason)
Note Sequence: payload index + duration/gap derivation formulas
2026-03-22 19:36:07 +00:00
you 6b487fdd8d Audio Lab: real-time playback highlighting
As each note plays, highlights sync across all three views:
- Hex dump: current byte pulses red
- Note table: current row highlights blue
- Byte visualizer: current bar glows and scales up

Timing derived from note duration + gap (same values the voice
module uses), scheduled via setTimeout in parallel with audio.
Clears previous note before highlighting next. Auto-clears at end.
2026-03-22 19:27:35 +00:00
you 443a078168 Audio Lab page (Milestone 1): Packet Jukebox
New #/audio-lab page for understanding and debugging audio sonification.

Server: GET /api/audio-lab/buckets — returns representative packets
bucketed by type (up to 8 per type spanning size range).

Client: Left sidebar with collapsible type sections, right panel with:
- Controls: Play, Loop, Speed (0.25x-4x), BPM, Volume, Voice select
- Packet Data: type, sizes, hops, obs count, hex dump with sampled
  bytes highlighted
- Sound Mapping: computed instrument, scale, filter, volume, voices,
  pan — shows exactly why it sounds the way it does
- Note Sequence: table of sampled bytes → MIDI → freq → duration → gap
- Byte Visualizer: bar chart of payload bytes, sampled ones colored

Enables MeshAudio automatically on first play. Mobile responsive.
2026-03-22 19:19:45 +00:00
you 54d7ec1a86 Audio Workbench: expand M2 parameter overrides — envelope, filter, limiter, timing 2026-03-22 19:00:48 +00:00
you 412008c56f Audio: eliminate pops — proper envelope + per-packet limiter
Three pop sources fixed:
1. setValueAtTime(0) at note start — oscillator starting at exact zero
   causes click. Now starts at 0.0001 with exponentialRamp up.
2. setValueAtTime at noteEnd jumping to sustain level — removed.
   Decay ramp flows naturally into setTargetAtTime release (smooth
   exponential decay, no discontinuities).
3. No amplitude limiting — multiple overlapping packets could spike.
   Added DynamicsCompressor as limiter per packet chain (-6dB
   threshold, 12:1 ratio, 1ms attack).

Also: 20ms lookahead (was 10ms) gives scheduler more headroom.
2026-03-22 18:58:33 +00:00
you 81a905a248 Packets page: O(1) hash dedup via Map index
packets.find(g => g.hash === h) was O(n) and could race with
loadPackets replacing the array. hashIndex Map stays in sync —
rebuilt on API fetch, updated on WS insert. Prevents duplicate
rows for same hash in grouped mode.
2026-03-22 18:55:22 +00:00
you 24bc8ce966 Audio: log2 voice scaling (up to 8), wider detune spread
1 obs = solo, 3 = duo, 8 = trio, 15 = quartet, 30 = quintet, 60 = sextet.
Wider detune (±8, ±13, ±18...) so stacked voices shimmer instead
of sounding like the same oscillator copied.
2026-03-22 18:45:47 +00:00
you 24e3e3b441 Audio: pass consolidated observation_count to sonifyPacket
Realistic mode buffers observations then fires once — but was
passing the first packet (obs_count=1). Now passes consolidated
packet with obs_count=packets.length so the voice module gets
the real count for volume + chord voicing.
2026-03-22 18:44:45 +00:00
you 193e63a834 Fix realistic mode: all WS broadcasts include hash + raw_hex
Secondary broadcast paths (ADVERT, GRP_TXT, TXT_MSG, TRACE, API)
were missing hash field. Without hash, realistic mode's buffer
check (if pkt.hash) failed and packets fell through to
animatePacket individually — causing duplicate feed items and
duplicate sonification.

Also added missing addFeedItem call in animateRealisticPropagation
so the feed shows consolidated entries in realistic mode.
2026-03-22 18:37:53 +00:00
you 0843a57761 Audio: fix pop/crackle — exponential ramps + longer release tail
Linear gain ramps + osc.stop() too close to release end caused
waveform discontinuities. Switched to exponentialRamp (natural
decay curve), 0.0001 floor (-80dB), 50ms extra headroom before
oscillator stop.
2026-03-22 18:31:36 +00:00
you 5f3732f400 CI: skip deploy on markdown, docs, LICENSE, gitignore changes 2026-03-22 18:28:56 +00:00
you 7b3d41922b Audio Workbench plan — packet jukebox, parameter overrides, A/B comparison 2026-03-22 18:26:49 +00:00
you 651bc8a1e6 Remove legacy playSound/🔇 button — MeshAudio is the only audio system now 2026-03-22 18:11:55 +00:00