17 Commits

Author SHA1 Message Date
MarekWo 233b967032 feat(pathanalyzer): remember filter settings between visits
Time range and the filter bar reset on every open, so a working set like
"Last 1 day + 2/3-byte" had to be re-entered each time. The controls are
now stored in localStorage - a personal working set, not device state,
so it stays out of the database.

Only user-driven changes are stored: programmatic ones, such as the
deep-link widening the range to 7 days to find an older message, must not
overwrite the saved set. Restoring is skipped entirely when arriving via
a deep link, where a saved filter could hide the very message the user
clicked through to.

The Routes segment length rides along - same "set it again every visit"
annoyance. A stored value for an option that no longer exists is ignored.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-26 18:45:17 +02:00
MarekWo f31e4e160c feat(pathanalyzer): map overlay toggles for repeaters and alternative paths
The map used to plot every located repeater on top of the selected route,
which buried the path the user actually asked for. Both extras are now
opt-in, via a Leaflet control in the map corner - the shared filter bar
would be the wrong place, since these apply to the Map view only.

- "All repeaters": the purple contact markers, off by default (also saves
  plotting ~650 markers on every render)
- "Alternative paths": the selected message's other echoes, each in its
  own light hue, keyed to a swatch on the matching sidebar row

Echoes of one message usually share a long prefix, so a segment is drawn
once only - the primary route claims it, and an alternative then shows
exactly where it diverges instead of hiding underneath. Each alternative
also gets a dot at its last resolved hop, since the difference is often
just the final one.

Toggling re-renders without refitting the bounds, so it never throws away
the viewport the user panned or zoomed to.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-26 18:35:06 +02:00
MarekWo d8d3427dc9 feat(pathanalyzer): chat route click opens the analyzer map deep-linked
Clicking a route in the chat path popup now opens Path Analyzer on the
Map view with that message selected and that exact echo path drawn,
instead of copying the route to the clipboard. Copy stays available as
a small per-route clipboard icon in the popup.

Deep link flow: popup click stores {packet_hash, path hex} in
window.paDeepLink; the modal show handler builds the iframe URL with
?hash=&path=; the analyzer resolves the message after load (widening
the time range once to 7 days if needed), matches the echo by raw path
hex (fallback: shortest), and switches to the map. A plain menu open
still loads the analyzer without any deep link.

Verified live via Playwright: clicked the 3rd (non-shortest) route of a
multi-route message - the map opened with exactly that echo selected,
including the 3-to-7-day widening retry (message was 4 days old).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-23 07:10:31 +02:00
MarekWo afb305a61e feat(pathanalyzer): Routes view - hop segment statistics + sequence filter
New 4th view counting consecutive hop segments (n-grams) across all
routed echo paths, regardless of position in the path:
- segment length selector (2/3/4 hops), table sorted by echo count by
  default; columns: Echoes, distinct Messages, As path end (how often
  the segment is the final part of the path reaching us)
- resolved contact names shown under the hash chips (ambiguous/unknown
  marked like elsewhere)
- row click fills the repeater filter with the segment and jumps to the
  message list
- the repeater filter now accepts consecutive sequences chained with
  > or an arrow (e.g. AFE6>6E9A or hash>name); single values behave
  exactly as before, spaces stay usable inside contact names
- view switcher buttons show icons only on xs screens so 4 buttons fit

Idea by Daniel - group paths by recurring hop sequences to see which
routes carry the most traffic.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-21 21:38:47 +02:00
MarekWo 0d9d9ecd57 feat(pathanalyzer): map view polish - channel label, auto path, 45vh map
- Show the channel name (dimmed, next to the sender) on map-view tiles
- Clicking a message now auto-selects and draws its shortest routed echo
  (fewest hops, ties -> first); re-clicking keeps the user's echo choice
- Mobile: map takes a fixed 45% of the viewport height and the path list
  gets the remaining space (was min 300px map / max 40% list)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-21 21:08:19 +02:00
MarekWo 944493a56e feat(pathanalyzer): collapsible filter panel on small screens
On viewports below 768px the toolbar collapses to a single line (view
switcher + Filters toggle + counter); filters expand on demand and an
active-filter count badge shows on the toggle when collapsed. Desktop
layout is unchanged (the panel renders with display: contents). Frees
most of the vertical space for the path list on the mobile Map view.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-21 20:50:11 +02:00
MarekWo 19c35f4a2b feat(pathanalyzer): combined 2/3-byte option in the HB filter
The path-hash-size filter accepts a comma list of sizes; a new
'2/3-byte' option matches messages with any routed echo using a 2- or
3-byte hash.

Verified live: 2/3-byte returns exactly the union of the 2-byte and
3-byte sets (220 = 208 + 12 with zero overlap misses on live data).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-20 12:14:50 +02:00
MarekWo c1853fe8d7 feat(pathanalyzer): map path styling and manual-pick undo
- Drawn paths now use red (#dc3545) for hop markers and polylines,
  clearly distinct from the purple base repeater dots (origin stays
  green, ambiguous candidates amber)
- Resolved hops render as numbered badges matching the legend order,
  with a permanent name label next to each point (origin included)
- Manual candidate assignments are reversible: a picked hop shows an
  undo icon in the legend, and a 'Reset picks' button clears every
  manual assignment on the current path

Verified live via Playwright: line/marker colors, badge numbers and
name labels, pick -> per-hop undo -> re-pick -> reset-all flow, no
page errors.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-20 11:46:32 +02:00
MarekWo 0ad26a66f8 fix(pathanalyzer): Messages and Repeaters views fit narrow screens
Rather than a card-per-row transform (too much scrolling for 400+
messages), narrow viewports (<=576px) keep the table but shed
secondary width:
- Hash, HB and Echoes columns hidden; packet hash + HB surface as a
  line at the top of the expanded detail row instead (still copyable)
- time renders as short date/time stacked on two lines (full timestamp
  on wide screens)
- sender/channel/message-preview columns capped with ellipsis, tighter
  cell padding, headers allowed to wrap
- stats view: Messages and As-last-hop columns hidden on phones
  (Repeater, Contact, Relayed, Avg SNR remain), contact names truncated
- expanded echo paths now wrap fully within the viewport, keeping every
  hop chip and the jump-to-map button reachable

Root cause of the first attempt failing: the media block sat mid-
stylesheet, so later equal-specificity base rules overrode it - it now
sits last, with a comment pinning it there.

Verified live via Playwright at 390px: zero horizontal overflow in
Messages (incl. an expanded 20-hop path, map button on-screen) and
Repeaters; desktop 1280px layout unchanged.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-19 21:53:57 +02:00
MarekWo 0ba0503547 feat(pathanalyzer): repeater filter matches by name as well as hash
The repeater filter input now accepts either form: pure-hex input still
prefix-matches hop hash tokens, and any input also matches the names of
contacts each token resolves to (pubkey-prefix candidates, memoized per
token). With 1-byte hashes a name can match via a colliding candidate,
so the filter is intentionally inclusive: it keeps messages where the
named repeater could be on the path.

Verified live via Playwright: hex prefix regression OK, name search
returns only messages with a token resolving to the named contact,
memoized pass over 606 messages takes <1 ms.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-19 19:44:32 +02:00
MarekWo 3522dbabef feat(pathanalyzer): message text filter
Free-text case-insensitive substring filter on message content, next to
the other toolbar filters; combines with them and is reset by Clear.

Verified live via Playwright: content match returns only matching rows
(5/606), combines with the sender filter, Clear restores the full set.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-19 19:42:42 +02:00
MarekWo 9574a7ebee feat(pathanalyzer): jump-to-map from echo rows, message preview in map sidebar
- Each routed echo line in the Messages table gets a map icon that
  switches to the Map view with that message + echo pre-selected and
  its path drawn; the sidebar scrolls the selection into view
- Map sidebar entries show the message content under the sender: one
  truncated line normally, full text when the message is selected;
  sidebar entry markup switched from innerHTML to DOM building so
  network-supplied sender/content strings are never parsed as HTML
- Fix: path drawing (fitBounds) now runs after invalidateSize in the
  same deferred step - drawing immediately after unhiding the map
  container computed bounds against a stale size and left the map at
  world zoom when jumping from the table

Verified live via Playwright: jump selects the right echo (route
chips match the sidebar entry), path draws at zoom 10, previews
truncate on unselected entries and expand on the selected one.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-19 19:36:11 +02:00
MarekWo 67b405251b feat(pathanalyzer): path hash size (HB) column and filter
- HB column in the message table: distinct hash sizes (bytes per hop)
  across the message's routed echoes, e.g. '1' or '1,2'; em dash when
  no routed echo
- HB filter (Any/1-byte/2-byte/3-byte): matches when any routed echo
  uses that hash size; 0-hop echoes are excluded since their stored
  hash_size is meaningless; combines with the other filters and is
  reset by Clear

Verified live via Playwright: 1B/2B/3B filters each return only
messages with a matching routed echo (183/166/9 of 606, zero false
positives), combined hops+HB filtering works, Clear resets.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-19 19:19:00 +02:00
MarekWo dca3d58232 feat(pathanalyzer): map view with path drawing (stage 5)
- Third view toggle: Map. Leaflet lazy-init on first activation with
  invalidateSize() once the container is visible; OSM tiles as in the
  main app map
- Base layer: repeater contacts with geo plotted as purple circle
  markers (name + pubkey prefix popup), from /api/contacts/cached
- Sidebar lists filtered messages that have routed echoes; click a
  message to see its echoes, click an echo to draw it
- Hop resolution by pubkey prefix: single geo candidate draws a solid
  hop point; collisions render all candidates as amber markers plus a
  legend row with clickable pick-chips (pick resolves the hop and
  redraws); unknown hops are listed as 'unknown / no position'
- Polyline follows path order with the sender (name-matched contact) as
  origin when available; segments bridging unresolved hops are dashed;
  map fits bounds to the drawn path
- Clear button erases the drawn path; stale selections are dropped when
  filters change underneath

Verified live via Playwright: 252 repeater markers, path + legend
render, ambiguous-hop candidate pick adds a polyline segment, clear
empties the layer, view toggling stays stable with no page errors.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-19 19:15:59 +02:00
MarekWo ca24d62ca6 feat(pathanalyzer): per-repeater statistics (stage 4)
- Messages / Repeaters view toggle in the toolbar; stats aggregate over
  the currently filtered messages so both views share one filter state
- Per exact hash token: Relayed (echoes containing the token anywhere),
  distinct Messages, As-last-hop count, and Avg SNR counted only over
  echoes where the token is the final hop (SNR is measured at our
  receiver, so it is never attributed to intermediate hops)
- Sortable numeric columns (default Relayed desc, nulls always last)
- Token -> contact enrichment via /api/contacts/cached?format=full
  pubkey prefix match: single candidate shows the name, collisions show
  'ambiguous (n)' with candidate names in the tooltip (matcher shared
  with the stage-5 map)
- Clicking a stats row applies that token as the message filter and
  switches back to the Messages view

Verified live via Playwright: aggregates match an in-page manual
recount exactly, sort orders correct, row-click cross-navigation works,
1-byte and 2-byte tokens kept distinct.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-19 19:12:34 +02:00
MarekWo 07d2c347ba feat(pathanalyzer): path display and filters (stage 3)
- Message rows expand to show each echo as per-hop repeater hash chips
  (A3 -> 3B -> 5E) with direction badge, SNR and received time; tokens
  split per that echo's own hash_size (same logic as showPathsPopup),
  0-hop echoes shown as 'Direct (flood, 0 hops)'
- Chip click copies the repeater hash, echo line click copies the comma
  route (parity with the chat path popup)
- Filter bar (client-side, combinable, 150ms debounce): hop count
  (any/0/1/2/3/4+, matches if any echo has that hop count), repeater
  hash token (hex-normalized prefix match, bridges mixed hash sizes),
  sender substring; Clear button appears when any filter is active
- 'N of M messages' counter and a dedicated empty state when filters
  match nothing

Verified live via Playwright: expand/collapse, both copy actions,
hops=2 yields only rows with a 2-hop echo (39/602), combined
token+hops filter, sender filter, clear resets to full set.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-19 19:01:23 +02:00
MarekWo 9668651a61 feat(pathanalyzer): Path Analyzer panel skeleton (stage 2)
Full-screen iframe panel wired per the add-fullscreen-panel checklist:
- /path-analyzer Flask view + standalone template (theme bootstrap before
  CSS, local vendor assets, Leaflet included for the stage-5 map view)
- path-analyzer.js: days selector (1/3/5/7), message table (time, channel,
  sender, preview, copyable packet hash, hops, echo count), spinner/empty
  states, local toast helpers fed from /api/ui/settings
- #pathAnalyzerModal in index.html, iframe src set on show.bs.modal for
  fresh data each open; FAB button + purple gradient class
- menu entry in #mainMenu (Tools) + placement radio row in Settings ->
  Appearance; registered in ITEM_PLACEMENT_DEFS/DEFAULTS (default: menu)
- style.css: #pathAnalyzerModal added to all three narrow-viewport
  fullscreen-override selector lists

Verified live via Playwright at 1280px and 390px, light + dark: modal
fills the viewport exactly, placement radios move the entry both ways,
iframe reloads on every open.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-19 17:14:20 +02:00