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

@@ -103,7 +103,7 @@ public:
if (_text_sz == 4) return 6 * BIG_TEXT_SCALE;
if (_text_sz == 3) return 17;
if (_text_sz == 2) return 12 * sc;
return (_use_lemon ? 6 : 6) * sc; // misc-fixed 6x9 is 6px wide
return 6 * sc; // misc-fixed 6x9 is 6px wide
}
int getLineHeight() const override {
int sc = scale();

File diff suppressed because it is too large Load Diff

View File

@@ -93,7 +93,7 @@ int16_t SH1106Display::drawLemonChar(int16_t x, int16_t y, uint32_t cp) {
// Font glyphs come from misc-fixed 6x9 (full Latin/Greek/Cyrillic, ascent 7) —
// baseline +7. The custom UI icons above keep +6, so they sit 1px higher.
if (cp < MiscFixed.first || cp > MiscFixed.last) {
if (cp >= 0x20) display.fillRect(x + sz, y - sz, 4*sz, 6*sz, _color);
if (cp >= 0x20) display.fillRect(x + sz, y - 7*sz, 4*sz, 6*sz, _color);
return x + 6 * sz;
}
const GFXglyph* g = &MiscFixedGlyphs[cp - MiscFixed.first];

View File

@@ -21,7 +21,7 @@ class SH1106Display : public DisplayDriver
uint8_t _color;
uint8_t _contrast;
uint8_t _precharge;
bool _use_lemon = true; // OLED is single-font (misc-fixed 5x7); the Lemon/default switch is retired here
bool _use_lemon = true; // OLED is single-font (misc-fixed 6x9); the Lemon/default switch is retired here
int _text_sz;
// Frame-skip: endFrame() hashes the GFX buffer (FNV-1a, no external dep — the
// CRC32 lib is only wired into e-ink builds) and skips the I²C flush when it's
@@ -57,13 +57,13 @@ public:
if (_use_lemon) return lemonXAdvance(cp);
return 6 * _text_sz; // built-in 5x7 font: 6 px advance per glyph
}
int getCharWidth() const override { return (_use_lemon ? 6 : 6) * _text_sz; } // misc-fixed 6x9 is 6px wide
int getCharWidth() const override { return 6 * _text_sz; } // misc-fixed 6x9 is 6px wide
int getLineHeight() const override { return (_use_lemon ? 9 : 8) * _text_sz; } // misc-fixed 6x9 box height
// Only the built-in classic font pads every measured string by one trailing
// advance column (see DisplayDriver::textWidthTrailingGap()); the lemon
// font's width comes from its own glyph table (ink-tight, no padding).
int textWidthTrailingGap() const override { return _use_lemon ? 0 : 1; }
void setLemonFont(bool) override { } // single-font: ignore toggles, stay misc-fixed 5x7
void setLemonFont(bool) override { } // single-font: ignore toggles, stay misc-fixed 6x9
bool isLemonFont() const override { return _use_lemon; }
void translateUTF8ToBlocks(char* dest, const char* src, size_t dest_size) override;
void setBrightness(uint8_t level) override;