docs: Path Analyzer map overlays and remembered filters

user-guide: the Map view now opens showing only the selected route, with
the two opt-in checkboxes (All repeaters, Alternative paths) described
alongside how alternatives are coloured and why only their diverging
stretches are drawn. The Filters section gains a paragraph on settings
being remembered per browser, including the deep-link exception.

architecture: added map layer ordering, the segment de-duplication that
keeps alternatives visible, the no-refit re-render on toggle, and the
localStorage filter persistence contract (user-driven writes only, skip
restore on deep link).

whatsnew: three user-facing entries under the pending release section.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
MarekWo
2026-07-26 18:47:38 +02:00
parent 233b967032
commit 95d96ecbd3
3 changed files with 14 additions and 2 deletions
+2
View File
@@ -113,6 +113,8 @@ The `/path-analyzer` panel (standalone iframe page, `path-analyzer.js`) is a rea
- **All filtering/stats/map/routes logic is client-side** over the bulk payload (hundreds of KB for 7 days — fine on a LAN): filters operate on per-hop tokens split with each echo's own `hash_size` (mixed 1/2/3-byte networks are real), so SQL-side token filtering was rejected. Every view always reflects the active filters for free
- **Four views** share the one payload: Messages (hop-by-hop echo detail), Repeaters (per-hash relay/SNR stats), Routes (consecutive hop-segment n-grams — user-selectable length 24, counted anywhere in a path), and Map (Leaflet path drawing). The repeater filter accepts a `>`-chained sequence (each element a hash prefix or contact name) matched as consecutive hops; Routes rows write such a sequence into that filter on click
- **Deep link** — `GET /path-analyzer?hash=<packet_hash>&path=<echo path hex>` opens straight on the Map view with that message selected and that exact echo drawn. Used by the chat path popup (`app.js``openPathInAnalyzer` stashes `{hash, path}` in `window.paDeepLink`; the modal's `show.bs.modal` handler builds the iframe URL). On load the analyzer resolves the message by `packet_hash`, widening the time range once to 7 days if it isn't in the current window, and matches the echo by raw path hex (fallback: shortest routed echo)
- **Map layers** — three `L.layerGroup`s added in draw order (base repeater markers → alternative echoes → selected path), both extras gated by opt-in checkboxes in a `topright` `L.Control` (the shared filter bar is wrong for view-specific state). Alternative echoes are coloured by their index in `echoView`, so a hue survives changing which echo is primary; segments are deduplicated against a `Set` of endpoint-pair keys that the primary path fills first, so alternatives render only where they diverge instead of underneath the primary line. Toggling calls `paRenderMapView(false)` — a full re-render (the sidebar swatches must follow) with `fitBounds` suppressed, so overlay changes never discard the user's viewport
- **Filter persistence** — toolbar controls plus the Routes segment length are mirrored into `localStorage` under `mc-webui-pa-filters` (browser-local working set, deliberately not device state in SQLite). Only user-driven handlers write, via `paApplyAndSaveFilters()`, so programmatic changes — notably the deep link forcing 7 days — never overwrite the stored set; restore is skipped entirely when `paDeepLink` is present, and stored `<select>` values that no longer exist as options are ignored. `paRestoreFilters()` ends with `paReadFilters()` so `paFilters`, the Clear button, and the mobile badge match the restored DOM before the first render
- **SNR attribution** — echo SNR is measured at our receiver, so stats credit it to the *final* hop only; intermediate hops get relay counts but never SNR
- **Hash→contact resolution** — pubkey-prefix match against `/api/contacts/cached?format=full` (memoized per token). 1-byte hashes collide by design; the map renders unresolved hops as amber candidate markers with manual pick, and the repeater-name filter is intentionally inclusive over candidates
- Legacy rows whose `pkt_payload` cannot be recomputed (missing channel secret) are returned with `packet_hash: null` and no echoes rather than dropped
+9 -2
View File
@@ -649,6 +649,8 @@ The filter bar applies to all four views at once and updates as you type:
A counter shows how much of the data set matches ("38 of 412 messages"), and **Clear** resets everything. On phones the whole filter bar collapses behind a **Filters** button (with a badge counting the filters you have set) so the results get the full screen — the view switcher and match counter stay visible.
Your settings are remembered by the browser, so a working set like "Last 1 day + 2/3-byte" is still in place the next time you open the analyzer — including the time range and the Routes view's segment length. **Clear** resets the filters and forgets them, and the counter plus the **Clear** button always tell you when something is still filtering. Opening the analyzer from a chat route is the one exception: it ignores your saved filters for that visit, so they can't hide the message you tapped through to (your saved set is left untouched). The settings live in this browser only — they are not stored on the device, so another browser or phone keeps its own.
### Messages view
A table of messages: time, channel, sender, text, packet hash (click to copy — the same hash analyzer services use), hop count, hash size, and echo count. Click a row to expand its routes:
@@ -684,13 +686,18 @@ Columns are sortable (echo count first by default). Click any row to jump to the
### Map view
Repeaters from your contact list that have a position are plotted as dots. Pick a message in the side list — each tile shows the sender, the channel it came from (dimmed, next to the sender), the time, and the message text. Its **shortest** route is drawn automatically the moment you pick the message, so you see a path with one tap; pick a different route from the list to redraw. The path is drawn hop by hop:
Pick a message in the side list — each tile shows the sender, the channel it came from (dimmed, next to the sender), the time, and the message text. Its **shortest** route is drawn automatically the moment you pick the message, so you see a path with one tap; pick a different route from the list to redraw. The map starts clean, showing only that route. The path is drawn hop by hop:
- Hops that resolve to exactly one known repeater become red numbered points (the number matches the legend order) labeled with the repeater's name, connected by a red line — clearly distinct from the purple background dots of uninvolved repeaters
- Hops that resolve to exactly one known repeater become red numbered points (the number matches the legend order) labeled with the repeater's name, connected by a red line
- When a short hash matches several contacts, all candidates are marked in amber and the legend lists them — tap the right one and the path redraws with your choice. Assignments are reversible: an undo icon next to a manually assigned hop reverts just that hop, and **Reset picks** clears every manual assignment on the current path
- Unknown hops (no matching contact, or no position) are listed in the legend and the line is drawn dashed across the gap, so you can see which parts of the route are certain
- If the sender is in your contacts with a position, it is added as a green origin point
Two checkboxes in the map's top-right corner add context when you want it. Both start switched off, and switching them does not move or re-zoom the map, so you keep the view you panned to:
- **All repeaters** — plots every repeater from your contact list that has a position, as purple dots. Useful for judging which repeaters a route passed by but didn't use; leave it off to keep the route itself uncluttered
- **Alternative paths** — draws the *other* routes your node overheard for the same message, each in its own light colour (blue, teal, orange, …). The matching coloured dot next to each route in the side list tells you which line is which, and tapping a line names the route and its SNR. Copies of one message usually share most of their route, so only the parts where an alternative actually **differs** are drawn — plus a dot where it ends, since often the last hop is the only difference
The eraser button clears the drawn path. On phones the map takes a fixed share of the screen (about 45%) and the message list gets the rest, so scrolling through routes is comfortable.
Everything in the Path Analyzer is based on what **your node** overheard — it's a local view of the mesh, not a global one. A route you don't see here may still exist; it just never reached your radio.
+3
View File
@@ -10,6 +10,9 @@ For deep technical notes, see [architecture.md](architecture.md). For the full g
### New features
- **The Path Analyzer map starts on the route you asked for.** The map used to plot every located repeater on top of your route, which buried the path you actually wanted to see. It now opens showing just that route, and two checkboxes in the map's top corner add the rest back when you want it: **All repeaters** brings back the purple dots of uninvolved repeaters, and **Alternative paths** draws the other copies of the same message your node overheard. Both start off, and switching them never moves or re-zooms the map, so you keep the view you panned to.
- **See where a message's routes actually differ.** With **Alternative paths** on, each of the message's other routes gets its own light colour, matched by a coloured dot next to it in the side list, so you can tell the lines apart — tap one to see its hops and SNR. Copies of one message usually travel most of the same way, so only the stretches where an alternative really diverges are drawn, plus a dot marking where it ends. Often that's the last hop alone, which used to be invisible under the main route.
- **The Path Analyzer remembers your filters.** Time range, hop and hash-size filters, the text searches, and the Routes segment length are kept between visits, so a working set like "Last 1 day + 2/3-byte" no longer has to be set up every single time. **Clear** resets and forgets them. Opening the analyzer from a chat route ignores your saved filters for that visit, so they can't hide the message you tapped through to. The settings are stored by your browser, not on the device — each browser or phone keeps its own.
- **Jump from a chat route straight to the map.** Tapping a route under a channel message used to just copy it to the clipboard. Now it opens the **Path Analyzer** on its map view with that message selected and that exact route already drawn — the quickest way to see where a message physically travelled. Copying isn't gone: each route in the popup keeps a small clipboard icon for pasting into the console's `change_path`.
### Reliability & polish