Commit Graph
21 Commits
Author SHA1 Message Date
dborupandClaude Sonnet 5 e9e828c7e9 feat: View Path shows estimated RF airtime for the whole flood
Reuses the Relay Airtime Share formula (LoRa Time-on-Air x distinct
relay count, issue #1768) against the single transmission a View Path
packet resolves to, sourced from the in-memory PacketStore via the
transmission ID GetPacketPath now captures. Rendered in the footer as
"~340ms estimated airtime (3 relays)", omitted cleanly when the store
doesn't have the transmission.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-26 10:59:09 +02:00
dborup d25d0253d9 feat: View Path status line shows elapsed time and total spread duration
Three additions, all using secondsAfterFirst/distanceFromFirstKm data
the response already carried -- no backend changes needed:

- "deepest reached N hops (Xs)" -- the deepest branch's own elapsed
  time, appended to the existing stat.
- "farthest reached Xkm (Ys)" -- new stat: the actual distance the
  farthest branch reached, plus its elapsed time. Previously only
  implied via the "touched" area list or a marker's hover tooltip,
  never stated as its own number.
- "fully spread in Zs" -- the largest secondsAfterFirst across ALL
  branches, not just farthest/deepest (a middling branch stuck behind
  a slow relay can still be the last one reached).

All three degrade cleanly (omitted, not "NaNs") when a branch's
timing is unknown.
2026-07-26 10:30:21 +02:00
dborup e851a43564 feat: View Path highlights both farthest and deepest routes when they differ
Following up on the previous fix (highlight by real distance, not hop
count): dborup asked why not show both when they diverge, rather than
picking one and letting the other blend into the secondary stations.

Now tracks two independent roles per branch -- farthest (by
distanceFromFirstKm) and deepest (by hop count) -- and highlights each
with its own color (accent for farthest, purple for deepest) plus its
own legend entry, when they're genuinely different branches. When
they're the same branch (the common case) it's still shown once,
combined, exactly as before. When no branch has distance data at all,
falls back to the single "deepest (most hops)" highlight, also
unchanged.

Also fixes a gap this surfaced: an observer-only branch (no relay
hops, e.g. a 0-hop direct reception) has no polyline and always FILLS
yellow regardless of role, so farthest vs. deepest were previously
indistinguishable on such a marker. The ring (stroke) color now
carries the role instead, so a highlighted observer reads as an
accent- or purple-ringed yellow dot rather than a plain one.
2026-07-26 09:56:40 +02:00
dborup 4cd8282650 fix: View Path highlights the actually-farthest branch, not just the deepest
The highlighted "primary" route was always branches[0] (most hops),
labeled "farthest-traveled route" -- but more hops doesn't mean more
geographic distance. A dense area can take many short hops; a couple
of long-range links can cover more real distance in fewer. dborup
caught the mislabeling; we now pick the branch with the largest
distanceFromFirstKm when any branch has that data (ties break toward
more hops, since branches[] stays deepest-first).

Falls back to the old hops-based pick only when NO branch has usable
distance data (sparse GPS coverage) -- and the legend/checkbox
wording switches to an honest "deepest (most hops)" in that case
instead of still claiming "farthest-traveled" for a branch nobody
actually measured.

The "deepest reached N hops" footer stat is unrelated and unchanged
-- it was already correctly hop-based.
2026-07-26 09:41:01 +02:00
dborup 51b76979ca feat: View Path adds show/hide toggles for area boundaries and approximate positions
Two more filter checkboxes alongside the existing declutter toggle:

- "Show area boundaries" -- starts checked (shapes visible by
  default, matching current behavior); unchecking removes them.
- "Show only approximate positions" -- appears only when at least
  one marker is approximate; filters down to just the
  neighbor-estimated stations, useful for sanity-checking how many
  (and which) positions on a packet are actually estimates rather
  than real fixes.

Both only appear when there's something to toggle (no area boundaries
= no checkbox, no approx markers = no checkbox), matching the
existing declutter toggle's convention.

Refactored the layer-visibility bookkeeping to support this: the
declutter and approx-only filters are independent and combinable, so
naively having each checkbox add/remove only its own layers breaks as
soon as both are active and one gets unchecked -- a layer that's
still excluded by the OTHER filter would incorrectly reappear.
Replaced with markerEntries/polylineEntries (tagged primary/approx)
and a single applyMarkerFilters() that recomputes every layer's
visibility from both checkboxes' current state together. Area
boundary visibility stays a separate, independent toggle since it's
an unrelated layer type.
2026-07-26 08:59:19 +02:00
dborup fe68991102 feat: View Path legend, declutter toggle, and shaded touched-area boundaries
Three follow-up improvements to the View Path map, all requested
together:

- A compact color/symbol legend (route color, approx marker, first-
  heard ring) replaces the old prose paragraph explaining the same
  things -- shorter intro text, faster to scan.
- A "show only farthest-traveled route" checkbox appears whenever a
  packet was heard by more than one station, to declutter busy
  20+-branch packets. Only added/removed as a Leaflet layer toggle;
  the primary branch's own markers/polyline are never touched.
- Every touched area is now shaded directly on the map (a real
  polygon when the area was drawn as one, a rectangle fallback for
  bbox-only areas) instead of only listed as text -- ties the
  "touched: X, Y" footer line to actual geography. Non-interactive so
  shapes never steal a click meant for a branch marker.

The shading needed PacketPathResponse.TouchedAreas to carry geometry,
not just labels -- changed from []string to []TouchedAreaShape
(label + polygon or bbox). annotatePacketPathTouchedAreas now looks
up the matched area's full config entry via AreaKeyForPoint instead
of just its label via AreaForPoint.
2026-07-26 08:44:20 +02:00
dborup d1743c6aaa fix: View Path "N approximate" counted chain appearances, not distinct nodes
A shared entry-point repeater near the sender commonly appears in
many branches' chains (one relay a dozen stations all heard the
packet through). The status count summed every chain appearance
instead of deduping by node identity, so a single approximate-
position node showing up in 11 branches reported "11 approximate"
-- dborup spotted this on a real packet and asked whether it could
be right; it wasn't, it was the same node counted 11 times.

Dedupes by publicKey (falling back to name when a point has none).
2026-07-26 08:20:13 +02:00
dborup 4652162149 feat: View Path shows every area the packet touched, uncapped
The ping-bot reply's "touched" list caps at 3 to keep a chat bubble
readable, but View Path's map has room to show the full set: every
configured area any point or observer on the path resolved to,
deduped and alphabetized.

No extra DB round-trip needed here (unlike the pong reply's version)
-- GetPacketPath already resolved every position, including the
neighbor-centroid approximation fallback, so annotatePacketPathTouchedAreas
just reads the lat/lon already on the response.
2026-07-26 08:05:05 +02:00
dborupandClaude Sonnet 5 4b4dc9f27f revert: remove View Path bridge-repeater highlighting
The "relays for 2+ distinct region scopes" definition doesn't hold up
in practice: most nodes end up carrying both a broad national/regional
scope (e.g. #dk, #eu) and several local ones, so the vast majority of
repeaters would eventually qualify as "bridge" -- the flag stops
meaning anything useful.

Removes IsBridge from PacketPathPoint/PacketPathObserver, the
markBridgeRepeaters handler step, the purple-outline styling, and the
dedicated test file -- keeps PublicKey on PacketPathObserver, since
the click-to-node-detail feature depends on it independently.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-24 19:27:56 +02:00
dborupandClaude Sonnet 5 6718c246b9 fix: View Path tooltips wrap instead of stretching across the whole map
Tooltip text grew long once role, approx/confidence, bridge, and
distance/timing info were all combined into one line. Leaflet's
default tooltip is white-space:nowrap, so a long one stretched into a
single unreadable line spanning the map instead of wrapping.

Adds a packet-path-tooltip CSS class (white-space:normal, max-width)
passed to every bindTooltip call in this component.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-24 19:19:57 +02:00
dborupandClaude Sonnet 5 d0205b0edd feat: View Path markers are clickable, navigate to node detail
Hop points already carried publicKey; PacketPathObserver got it in the
bridge-highlighting commit. Clicking any marker with one now closes
the modal and navigates to #/nodes/{pubkey} -- the same hash route the
rest of the app already links to (see e.g. public/channels.js). A
marker with no publicKey (e.g. a bridge-type observer keyed by device
name, not pubkey) gets no click handler and stays inert. Tooltip gets
a "click for node detail" hint when applicable.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-24 18:34:50 +02:00
dborupandClaude Sonnet 5 3c8ec35ea2 feat: View Path shows distance from 'first' in km
Adds DistanceFromFirstKm to PacketPathBranch: the great-circle
distance (haversine) between a branch's own Observer and First's
Observer. Zero for First itself. Deliberately omitted when either
side is positioned via Approx (a neighbor-centroid estimate) -- a
distance computed against a guess isn't a real measurement worth
surfacing.

Rendered in the observer tooltip as "42.3 km away" alongside the
elapsed-time label, giving a concrete sense of how far the flood
physically reached, not just how many hops or how long it took.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-24 18:23:05 +02:00
dborupandClaude Sonnet 5 575743efda feat: View Path highlights confirmed bridge repeaters
Adds IsBridge to PacketPathPoint/PacketPathObserver and PublicKey to
PacketPathObserver (needed to look bridges up, and useful on its own
for future features). Set by a new markBridgeRepeaters handler-level
step in routes.go, not GetPacketPath itself, since the underlying data
(TransportedScopes per repeater) lives in the in-memory store, not
SQL -- same "relays for 2+ distinct regions" definition and data
source as the Foreign Traffic tab's existing Bridge badge
(ScopeStatsResponse.bridgeRepeaters), just applied to whichever
handful of pubkeys one packet's path touches.

The map gives a bridge repeater a bold purple outline (on top of
whatever primary/approx styling already applies) plus a tooltip note,
so the mesh's backbone nodes stand out regardless of which branch
they're in.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-24 18:16:15 +02:00
dborupandClaude Sonnet 5 d5214006d6 feat: View Path shows a role icon for observers with a known node role
PacketPathPoint already carried Role; PacketPathObserver now does too
(from the observer's own nodes row, when it's known as a mesh node
itself and not just an MQTT/API listener). Rendered as a small icon
prefix in the tooltip (📡 repeater, 🏠 room, 📱 client, 🌡️ sensor) --
markers stay plain circleMarker dots throughout, since a role-specific
shape would clash with the color/dash coding already carrying primary,
approx, and observer meaning.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-24 18:03:25 +02:00
dborupandClaude Sonnet 5 4ecb9c3902 feat: scale View Path approximate markers by neighbor confidence
nearestPositionedNeighbor now also returns how many positioned
neighbors fed the weighted centroid and the widest distance between
any two of them (0 with a single contributor). Exposed via new
ApproxNeighborCount/ApproxSpreadKm fields on PacketPathPoint and
PacketPathObserver.

The map scales the approximate marker's size/opacity accordingly: one
neighbor (or several that disagree widely) renders as a bigger,
fainter ring; several agreeing neighbors render tighter and more
solid. The tooltip also states the contributor count.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-24 17:44:19 +02:00
dborupandClaude Sonnet 5 8e61a78d32 feat: View Path shows how long after 'first' each station heard the packet
Adds SecondsAfterFirst to PacketPathBranch: the gap between the
earliest-arriving observation (First) and this branch's own deepest
observation. Zero for First itself. Rendered in the observer's tooltip
as "+4.7s" (or "Nm Ss" for longer gaps, "first to arrive" for zero) --
gives a sense of the propagation order across the flood, not just
depth/breadth.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-24 17:38:15 +02:00
dborupandClaude Sonnet 5 09b84f372b fix: make approximate View Path markers actually visible
A plain hollow (fillOpacity:0) dashed ring at normal marker size blended
into the map tiles and was easy to miss entirely. Bumped it to a larger
radius (+4px), thicker dashed stroke, and a faint fill so it reads as a
soft, deliberately-imprecise blob instead of vanishing against the
background.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-24 17:10:45 +02:00
dborupandClaude Sonnet 5 5e6e6252b4 feat: View Path falls back to the nearest positioned neighbor for unpositioned hops/observers
A hop or observer with no position of its own (no self-advertised GPS,
no name match, no IATA) previously stayed unplotted entirely. Adds a
last-resort fallback: look up its strongest neighbor_edges neighbor
(ranked by observation count) and, if that neighbor has a real
position, borrow it as an approximate stand-in -- flagged via a new
`approx` field so callers never mistake it for a real fix.

The View Path map renders approximate markers hollow and dashed
instead of solid, with a status-line callout ("N approximate (via
nearest neighbor)"), and the "N hops without a known position" count
drops accordingly for whichever ones now have a stand-in.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-24 17:02:38 +02:00
dborupandClaude Sonnet 5 a4f927c23a feat: View Path marks the earliest-arriving observation as a landmark
GetPacketPath's branches are sorted deepest-first, so the map had no
natural "where did this start" anchor. Adds a `first` field: the
single earliest-arriving observation across every station regardless
of observer or hop depth (the same "first observation wins" pick
already used for a ping message's own meta line), positioned the same
way as any other observer (own GPS, then name match, then IATA).

The map draws it as a green ring on top of everything else, since it
usually coincides with one of the already-plotted branch dots, plus a
status-line callout -- an approximate landmark for where the message
entered the visible mesh.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-24 15:47:52 +02:00
dborupandClaude Sonnet 5 2f686d40c6 feat: View Path map shows every station that heard the packet, not just the deepest one
GetPacketPath used to resolve only the single farthest-traveled
observation and discard the rest. It now returns one branch per
distinct station (kept at that station's own deepest observation),
each with its own hop count and, where resolvable, relay chain --
so the map visualizes the packet's full flood spread instead of one
route. Stations whose path never resolved, or who heard the packet
directly (0 hops), still contribute a branch via their own position,
rather than being silently dropped.

The "View path" link in the ping-bot reply no longer requires hops > 0
to show, since even an all-direct ping still has observer spread worth
plotting.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-24 11:14:22 +02:00
dborupandClaude Sonnet 5 5842d85abd feat: "View path" map for the ping-bot reply
New GET /api/packets/{hash}/path resolves a packet's DEEPEST observation
(same "farthest leg is more informative" reasoning as the ping-bot reply
itself) to a geographic point sequence: each relay's name/role/lat/lon in
path order, plus the hearing observer's position (from its configured
IATA code, like the Wardriving tab). Hops that have never advertised a
GPS position come back with null lat/lon rather than being dropped, so
the frontend can draw a gap instead of guessing.

Frontend: public/packet-path-map.js is a small on-demand Leaflet modal
(reuses node-reach-map.js's tile/marker conventions, but draws an ordered
chain instead of a star) opened via a new "View path" link on the
ping-bot reply -- shown only when there's an actual multi-hop route and
packet hash to look up. Kept general (keyed by packet hash, not
ping-specific) since any packet with a resolved path could use it later.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-23 15:38:52 +02:00