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