feat(admin): typed Radio/Routing field editors; pre-release audit fixes

Tools > Admin: split the Radio tab's single "f,bw,sf,cr" comma-string
field (free-text keyboard) into 4 independent, type-appropriate rows --
Frequency (same digit-cursor DigitEditor Settings/Repeater use locally),
Bandwidth/Spreading factor/Coding rate (discrete-set LEFT/RIGHT stepping),
plus TX power and the 3 Routing numeric fields as number steppers and
Repeat as an ON/OFF toggle. Edits happen locally (zero mesh traffic per
keystroke); only Enter sends one combined `set`, Cancel sends nothing.

Full pre-release audit of all 16 commits since v1.22 (5 parallel focus
areas: display/font, keyboard/messages, Nodes/Admin, Bot/channels,
UITask+NodePrefs schema) turned up and fixed:

- Admin: fetched FK_NUMBER values weren't clamped to the field's range,
  so a value already out-of-range could get stuck unreachable; fixed
  commit-time float format noise (%.6f -> %.3f); Cancel/failed-login
  always returned to the Nodes picker even when Admin was opened
  directly from a node's Hold-Enter action -- now returns to wherever
  it was actually opened from (AdminScreen::_from_picker).
- Keyboard: one-shot Shift was consumed after the *first* T9 multi-tap,
  so cycling to the 2nd/3rd candidate always came out lowercase --
  fixed by caching the cycle's caps state (t9_caps).
- Messages: history is numbered newest-first, so a message arriving
  while scrolled up to an older one silently relabeled the view onto a
  different message -- selection now shifts with the insert.
- Channels: onChannelRemoved() didn't clear ch_notif_override/
  ch_notif_muted/ch_fav_bitmask despite its own contract comment
  requiring it (now far more reachable via the on-device Delete);
  an all-zero hex secret silently self-deleted the channel it was
  just saved into (collides with the empty-slot sentinel) -- rejected.
- Room login: isRoomLoggedIn() indexed the login-tracking ring
  directly instead of via its head offset, silently wrong once the
  ring wraps (8+ rooms/session) -- the new on-device Logout depends
  on this being right.
- Font/display: removed the now fully-inert Settings > Display > Font
  toggle and the dead LemonFont.h (retired by the earlier misc-fixed
  font unification, zero remaining includes); fixed a copy-pasted
  "5x7" comment (font is 6x9), two meaningless dead ternaries, and an
  OLED/e-ink inconsistency in the undefined-glyph fallback box offset.
- AdminField's `kind`/bounds fields no longer rely on default member
  initializers inside aggregate-init: this toolchain's actual nRF52
  build (unlike env:native) has no explicit -std= override, so it
  predates C++14's aggregate-with-default-member-initializer rule.
  Given an explicit constructor instead -- portable regardless of
  standard, all existing field-table literals unchanged.

Docs updated to match: tools_screen.md (Diagnostics as a 3-tab
carousel, was documented as one flat screen), message_screen.md
(chat bubbles, newest-at-bottom, cursor mode, secret validation),
settings_screen.md (dropped the dead Font row), solo_ui_framework.md
(header menu_hint signatures, icon priority-drop, KeyboardWidget's
T9/alphabets/cursor-mode). release-notes.md gains the v1.23 section
covering all of the above plus the other 15 commits since v1.22.

Build-verified on WioTrackerL1_companion_solo_dual.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Jakub
2026-07-17 19:38:51 +02:00
parent e439dd0fbe
commit 453dc5e570
20 changed files with 396 additions and 1605 deletions

View File

@@ -1,4 +1,45 @@
## MeshCore Solo Companion Firmware v1.22
## MeshCore Solo Companion Firmware v1.23
### What's new
- **Nodes is now the single node hub.** "Nearby Nodes" is renamed **Nodes** and gains on-device contact management — no phone app needed: **Hold Enter** on a row opens **Add contact** (for a scanned-but-unknown node), **Favourite/Unfavourite** (pins to the first free Favourites Dial slot, shown as a star in the list), and **Delete contact** (confirm first, defaults to Cancel). The filter strip (**LEFT/RIGHT**) is now a visible, wraparound tab carousel instead of a hidden mode. The old read-only **Recent adverts** home page is retired — its passively-heard nodes now simply show up in the Nodes list (All filter) — so there's one list instead of two.
- **On-device channel management.** Messages Channels gets a **"+ Add channel"** row and per-channel **Edit**/**Delete** — create or join a channel by typing either a passphrase (hashed to the channel secret, same idea as a room password) or the exact 32-hex-character key from a channel QR code. No phone app required.
- **Tools Admin — administer a remote repeater/room from the device.** Log into a node you have admin rights on (same self-healing saved-password handshake as room logins) and browse its settings in category tabs — **System** (name, owner info), **Radio** (Frequency/Bandwidth/Spreading factor/Coding rate/TX power), **Routing** (Repeat/Advert interval/Flood advert interval/Max hops), **Actions** (reboot, send advert, sync clock, …) — plus a free-text **Custom command…** escape hatch (with **{}**-key command-name completion) for anything not covered. Radio and Routing fields use a type-appropriate editor instead of raw text — a digit-cursor for Frequency (the same widget Settings' own Radio screen uses locally), discrete-set stepping for Bandwidth/Spreading factor/Coding rate, plain number steppers for the rest, and an ON/OFF toggle for Repeat — adjusting a value costs no mesh traffic until you actually confirm it with **Enter** (**Cancel** sends nothing). Reachable either from **Tools Admin** (opens the same Nodes list to pick a target) or directly via a repeater/room's own **Hold Enter Admin** action.
- **Auto-Reply Bot gains a third target: room servers**, alongside DM and Channel, with its own trigger/reply/commands — the screen is redesigned as a circular tab carousel (**Direct/Channel/Room/Other**, **LEFT/RIGHT** to switch, **UP/DOWN** within a tab) instead of one long list. Each target's **Enable** and **Commands** toggle is now fully independent (they used to secretly share the DM tab's setting for Channel/Room too). New `{name}`/`{hops}` reply placeholders, a DM allow-list (all chat contacts or favourites only), and a dedicated stepper for Quiet Hours.
- **Keyboard: more languages, better editing.** Eight more per-language keyboards — Polish, Czech, Slovak, German, French, Spanish, Portuguese, Nordic — join Cyrillic and Greek under **Settings Keyboard Alphabet**, each with a linguistically complete letter set in both the ABC grid and T9 multi-tap groups. Shift is now **one-shot** by default (capitalises just the next letter; **Hold Enter** on Shift toggles the old sticky caps-lock back on), **Hold Enter** on Backspace clears the whole field, and a new **Hold Enter** cursor-positioning mode (**LEFT/RIGHT** move, **UP/DOWN** jump to start/end) lets you edit or insert anywhere in what you've typed, not just at the end.
- **Messages get a messenger-style history.** History bubbles are now sized to their content and anchored **right** (outgoing) / **left** (incoming); the list runs **newest at the bottom**, growing upward as you scroll into the past; the **[+ send]** compose button moved to the right edge to match.
- **Clock Tools: alarm repeat.** The Alarm screen gains a **Repeat** row (Off/Daily/Weekdays/Weekends) — Hour/Minute merge into one **Time** row edited with an HH:MM digit cursor, and **Repeat**/**Armed** now respond to **LEFT/RIGHT** like every other Settings field instead of Enter-only.
- **Diagnostics becomes a tab carousel** (Live/System/Font): **System** adds firmware version + build date, device model and the active radio parameters; **Font** is a rendering test card with one sample line per script the on-device font covers, so coverage can be eyeballed directly.
- **One unified display font.** OLED and e-ink now both draw a single misc-fixed 6×9 font (full Latin/Greek/Cyrillic coverage) generated by a new BDF-to-GFX converter (`tools/bdf2gfx.py`), retiring the old Default/Lemon switch and the keyboard's per-render font-swap workaround.
- **Discoverable context menus.** Every screen with a **Hold Enter** action menu now shows a small **≡** glyph in its header, which highlights while the menu is open — so the shortcut no longer has to be guessed.
- **Status bar and list polish** — background-mode icons (Bluetooth/GPS fix/alarm/mute/auto-advert/trail/live-share/repeater/battery) now drop by priority once space runs out, instead of crushing the node name down to a couple of characters; unread counts everywhere are drawn as a filled pill badge; DM/Channel history headers match every other screen's header style; the default home-page carousel is trimmed to Clock/Tools/Shutdown/Favourites/Map (Recent/Radio/BT/Advert/GPS/Sensors are still available, just opt-in under Settings Home Pages — existing users' saved layouts are unaffected).
- **On-device room Logout** — Messages' room context menu gains a **Logout** entry (mirrors the app's own logout) alongside the existing **Login…**, forgetting the saved password so the next open prompts for one again.
### Fixes
- **Admin's typed Radio/Routing fields could get stuck on an unreachable value** — a field fetched from a node whose real value sat outside this screen's declared range (e.g. set by a different firmware/build) rejected every further adjustment in both directions. Fetched values are now clamped into range on entry.
- **Admin: the CLI's `"> "` reply-decoration prefix leaked into the pre-filled keyboard** when editing Name/Owner info (and the old single Radio-string field) — stripped once, consistently, before either the typed editors or the keyboard consume a reply.
- **Admin: Cancel / a failed login always dropped you onto the "pick a node" list**, even if you'd opened Admin directly from a node's own Hold Enter action while just browsing Nodes — it now returns to wherever you actually came from.
- **A message arriving while you'd scrolled up to an older one could silently swap in a different message at the same on-screen position** — history entries are numbered newest-first, so an insert used to shift every older message's index without updating your selection. The selection now shifts with it, so you keep looking at the same message.
- **One-shot Shift + T9 multi-tap didn't capitalise the letter you actually landed on** — Shift was consumed after the first tap of a cell, so cycling to the 2nd/3rd/etc. candidate always came out lowercase regardless of Shift. It now capitalises whichever candidate you settle on.
- **Deleting a channel could leave stale notification/favourite state behind** for whatever channel later got added into that freed slot — channel delete now also clears the per-channel notification override/mute and favourite flag, matching the existing cleanup for the bot/live-share/melody fields.
- **A hex channel secret of all zeros silently self-deleted the channel you'd just saved** — that value is reserved internally as the empty-slot marker. Now rejected up front ("Invalid secret") instead of failing invisibly after the save.
- **A room's logged-in status could be misreported after 8+ distinct rooms were logged into in one session** — the tracking ring's lookup didn't account for wraparound (only its insert/remove paths did), which the new on-device Logout now depends on for showing the right menu item.
- **Bot screen's tab order** now reliably starts on **Channel** even after the later room-target tabs were added (double-checked against the earlier "starts on Channel" fix, which the tab reshuffle could otherwise have silently undone).
- Reverted **corner-anchored context-menu popups** back to centred — looked bad on some screens; the header's **≡** discoverability hint stays.
- **Unread pill badge digits weren't centred** — the classic built-in OLED font always pads a measured string by one trailing column regardless of the glyph, throwing off centring math that assumed symmetric padding.
- **Status bar battery icon** stood 2px taller than its neighbours and its charge nub could drift off-centre at certain box heights — now sized and centred consistently with the rest of the row.
- Removed the now-inert **Settings Display Font** toggle, a leftover from the font unification above with no remaining effect.
### Under the hood
- **Shared `TabBar.h`** extracted from Nodes/Bot's independently duplicated tab-carousel rendering (Admin and Diagnostics are now the 3rd/4th consumers) — also fixes a tab that didn't quite fit vanishing outright instead of ellipsizing.
- **`MyMesh::setChannelLocal()`** factors out the setChannel/saveChannels/onChannelRemoved sequence previously duplicated across the BLE app's two `CMD_SET_CHANNEL` branches, now shared with the new on-device channel editor.
- **One canonical Admin entry point** — `UITask::openAdminFor()` / `AdminScreen::startFor()` — reached identically whether Admin is opened from Nodes' Hold-Enter action or the target picker.
- **`KeyboardWidget` reworked to UTF-8 codepoints** throughout (insert, backspace, T9 in-place cycling), so a multi-byte alt-alphabet character can never be split.
- **`NodePrefs` schema grew twice this cycle** — `alarm_repeat_mask`/`keyboard_alt_alphabet`, then the bot room-target/per-target-independence fields — each verified via a standalone host compile and `sizeof`/`offsetof` check, with the schema sentinel bumped both times; existing users' prefs upgrade in place with safe defaults.
### What's new