feat(mesh): show which repeaters relayed/confirmed a message's path

Extends the existing single-boolean channel relay-echo marker into a
full count + list of distinct confirming repeaters, since each
repeater retransmit already appends its own identity hash to the
packet's path and the echo-matching hash deliberately ignores that
mutable path -- so every distinct repeater's echo of one send now
matches the same tracking slot instead of only the first.

Symmetrically captures the hop path a received DM/channel message
actually took, so a new "Path"/"Relayed by" row in the existing
Hold-Enter Options popup can show the resolved sequence of repeaters
(by contact name, or a hex fallback for an unknown one).

Also fixes a real bug caught during testing: the popup row's own
label ("Path (N hops)"/"Relayed by (N)") was built into a stack-local
buffer handed to PopupMenu, which only stores the pointer -- it
rendered as garbage once the building function returned. Moved to a
persistent member buffer.

Bumps the dev-build fallback version and adds release notes/docs
for this plus the two other 1.27 features already on this branch
(BLE retry backoff, marquee-scroll for selected long text).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Jakub
2026-08-30 18:04:06 +02:00
co-authored by Claude Sonnet 5
parent 389f3f7a36
commit bdd2ed379d
10 changed files with 281 additions and 57 deletions
+11 -2
View File
@@ -104,8 +104,17 @@ Drawing helpers (all clip/measure for you):
in the header, so the menu is discoverable without already knowing the
shortcut; `menu_open` highlights it while the menu is actually up.
- `drawSelectionRow(x, y, w, h, sel)` — the highlight bar behind a list row.
- `drawTextEllipsized(x, y, max_w, str)` — truncates with ``; **use this for
any user string** (names, labels) so long/UTF-8 text can't overrun.
- `drawTextEllipsized(x, y, max_w, str, selected=false)` — truncates with ``;
**use this for any user string** (names, labels) so long/UTF-8 text can't
overrun. Pass `selected=true` for the currently-selected row and the text
that doesn't fit marquee-scrolls into view (pause → scroll to the end →
pause → scroll back), instead of just sitting behind the ellipsis; returns
the ms until the next redraw is needed for that animation to stay smooth
(0 when nothing is scrolling) — thread it into your screen's own `render()`
return value the same way you already clamp for anything else that needs a
faster redraw. Only one row UI-wide marquees at a time (whichever is
currently selected), so there's no risk of two animations racing each
other for the shared timing state.
- `drawTextCentered(mid_x, y, str)`.
- `translateUTF8ToBlocks(dst, src, n)` — map UTF-8 to the panel's glyph set for
*display only*. Never run text through it before sending it over the air or
@@ -64,7 +64,7 @@ Posting to a **room server** needs a login handshake — the device does this on
Messages appear as chat bubbles sized to their content — **right**-anchored for outgoing, **left** for incoming — with sender name and a compact age indicator (`3m`, `2h`, `>1d`) in the top-right corner. List runs **newest at the bottom**; opening a history starts at the latest message, scrolling up goes further back.
**Short Enter** on a message opens it in fullscreen. **Hold Enter** — on a history row or in fullscreen — opens the same options menu: Reply, plus **Navigate** / **Save waypoint** when the message contains a location (see Fullscreen message view). You don't need to open the message first.
**Short Enter** on a message opens it in fullscreen. **Hold Enter** — on a history row or in fullscreen — opens the same options menu: Reply, plus **Navigate** / **Save waypoint** when the message contains a location, and **Path** / **Relayed by** when hop data is available (see Fullscreen message view). You don't need to open the message first.
---
@@ -89,6 +89,13 @@ If the message is a reply addressed to someone (`@[nick]`), a **To: nick** bar i
A location is any `lat,lon` pair in the text — exactly what the `{loc}` placeholder inserts — so you can navigate to anything a contact shares. A `[WAY]lat,lon label` share also carries a name, used as the waypoint label. This works on DMs and channel messages, incoming or outgoing.
When the entry has hop data recorded, the menu also adds one more row:
- **Path (N hops)** — on a received message (DM or channel), lists every repeater the message actually travelled through to reach you, oldest hop first.
- **Relayed by (N)** — on your own channel post instead, lists every distinct repeater heard rebroadcasting it back into the mesh (order isn't meaningful here — each one heard it independently, not as a chain).
Selecting the row opens a read-only list of the resolved hops — each shown as the matching contact's name where one is known, or a short `?AABB`-style hex tag for an unrecognised repeater. Only repeaters within range of the message's actual travel — or, for **Relayed by**, within your own device's radio range — can ever be identified this way; a message with no recorded path (e.g. a zero-hop send, or one sent before any repeater relayed or echoed it) doesn't show this row at all.
---
### Context menu — contact list