My earlier sweeps searched for strings I could think of, which is why you
kept finding more. scripts/build/i18n-scan-unwrapped.py now parses every
call that takes user text — LVGL setters (label/checkbox/dropdown/roller/
placeholder/tab/table/msgbox), showAlert, showConfirm, appPageBegin,
createSettingsModal and the settings row helpers — splits the argument list
and reports any human-readable literal not wrapped in TR(). It found 55;
49 got wrapped (--fix does it mechanically), 6 skipped as brand/technical
(WADAMESH, CPU, PSK, OK, and two unit readouts). It now reports zero.
The 33 newly reachable keys are translated in all 13 languages: the Flood/
Zero-hop scope buttons, Share QR, Search contacts, Play / Set as
notification sound / Set as wallpaper, Probe now, On map, Install update,
Save update bin to SD, Choose .wav from files, Power, Clear history, the
Bluetooth/MQTT/Logs/System info sheet titles, both dropdown option lists,
the size preset list, Import, the URL bar, and Snake's New game.
Two format bugs behind the last stragglers: keys whose text begins with
LVGL recolor markup ('#7A7F87 Wardrive: ...#') were dropped as comment
lines by BOTH the device loader and the audit — a line is a comment only
when it has no tab, fixed in both. And several rows had been written with
literal '\xE2\x80\xA6' text where the file needs the real character; the
loader only unescapes \n \t \\, so those keys never matched.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Both were built from the same template and carried the same three defects, none of
which were really about their own code: they hand-rolled the page chrome that every
other app page gets from the shared machinery.
New src/ui-touch/AppPage.h exports that machinery (appPageCreateRoot / Begin / End /
DeleteRootAsync) so a self-contained module can build the same page the in-file tool
pages do, and both modules now use it.
1. GEOMETRY. Both positioned their overlay against a local `kTopBar = 22`.
STATUSBAR_H is a RUNTIME value: SC(22) once the UI scale is above 100%, and
SB_TOP_PAD + SB_ROW*2 on the T-Display P4's two-row bar. On those boards the
page sat too high and its own title + close button landed UNDERNEATH the real
status bar, which is what "the airtime app has issues" looked like. Geometry now
comes from the live bar height, so a page cannot disagree with the bar.
2. NAVIGATION. Neither installed s_apppage_title / s_apppage_close, so each needed
its own case in statusBarTapCb and its own clause in the bar-foreground gate, and
neither reached statusBarReaderBackCb — the PRESSED fallback that exists because
the cap-touch swipe detector can drop the lone CLICKED. On a touch-only board
that left no reliable exit at all. They now install the standard hook, get the
tall "< Airtime" / "< Snake" bar, and both special cases in UITask are deleted.
3. TEARDOWN. closeCb ran `lv_obj_del(root_)` and `delete this` synchronously from a
child button's own event callback, freeing the object LVGL was still dispatching
to. Teardown is now async throughout: lv_obj_del_async for the root, lv_async_call
for the instance, one single dismiss() path. Snake's canvas buffer moves to a
destructor so it is freed after the queued root delete released the canvas.
Also fixes what the rework exposed: applySwipeGesture had swallow-guards for Snake,
sliders, chats, popups, the control centre and settings sheets, but none for app pages
— so a horizontal swipe inside Airtime reached the tab switcher and jumped to the Map
(reported on device). An opaque CLICKABLE root only blocks CLICKS; that detector reads
the touch hardware directly and never consults LVGL hit-testing. One guard now covers
every full-screen page, Spectrum and the rest included, which had the same bug.
Bar titles are copied into module-owned storage rather than holding TR()'s pointer:
TR returns the static table cell for a plain key but a recycled 4-slot ring for an
icon-prefixed one, and s_apppage_title keeps the pointer for the page's lifetime.
Builds clean on V4, T-Deck and the P4 IDF target; flashed + boot-verified on the P4.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
P4-focused beta. Non-P4 boards only pick up minor shared exit-trap code, nothing user-facing.
- Snake + full-screen apps (Monitor/Spectrum/Discover/Web mirror/Advert) exit on status-bar tap
- Screen timeout / standby / burn-in dimming wired for the AMOLED (setBrightness path)
- UI scale pinned to 100% on the P4 (text overflow above 100%)
- No hard-lock on screen-off (P4 has no lock screen yet)
- GPS: L76K detect at 9600 baud, skip boot-detect race, 4096 RX buffer
- Control center dropdown taller on tall screens so the GPS line clears the toggle buttons
- P4 LCD SKU: HI8561 backlight PWM on GPIO51 (fixes the black screen)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Audited every user-visible string sink across the touch UI and closed the
gaps that had accumulated since the language table was last touched:
- TR() now strips a leading LVGL-symbol glyph (+ spaces) off the lookup key
and rebuilds the label, so icon-prefixed menu/sheet rows ("<glyph> Copy")
translate instead of silently falling back to English.
- Wrapped 95 bare literal sinks (settingsRowLabel / showAlert / showConfirm /
lv_label_set_text / dropdown / placeholder / checkbox) in TR(); unwrapped 29
format-only strings that never should have been keys.
- Added 307 missing rows to kI18n[] covering all 11 non-English languages
(NL DE FR ES IT RU UK BG SR EL PT-BR); keys byte-identical, printf
placeholders and %-order preserved, Serbian in Cyrillic.
Builds green on T-Deck + Heltec V4; Tanmatsu app 3,169,296 B (107 KB under
the AppFS 50-page load ceiling).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Battery chart: stats line spans the full width so the CPU MHz figure no longer
wraps; the CPU series + chip now use the RF-monitor noise-floor grey (0x4A5256).
Telemetry: the window no longer auto-requests on open — it shows history and a
Request button (async refresh glyph, left of the gear) sends a poll. Legend text
recoloured to the axis-note grey instead of near-black.
Map: the + / - overlay buttons are replaced by a single zoom button that toggles
a full-width zoom slider along the bottom of the map (tap again to hide); the
chosen level is persisted across reboots and wins over the first-open auto-snap.
New 'Night mode (invert)' toggle in Map options inverts tile colours at render
time only (cache untouched), persisted.
Snake: pause/resume button next to the close X.
Status bar: hold for 3 s to capture a full-screen screenshot to
/screenshots/<time>.bmp on the SD card (16-bit BMP — fastest viewable format;
the hold suppresses the control-center tap).
Prefs: cfg v5 adds map_night + map_zoom.
Signed-off-by: Dan Vybiral <dan.vybiral@greencode.cz>
- trackball now steers Snake (UITask routes motion via SnakeGame::isOpen()/steer()
and hides the soft cursor on that page).
- a "New game" button gates the start so it doesn't move on open; tap restarts
after a game over.
- slower tick (220 -> 300 ms).
- playfield fills the unoccupied screen (dynamic cols x rows at kCellPx instead of
a small fixed square).
- the tab bar is hard-locked while Snake is open so a stray swipe/tap can't slip
through to the app switcher (e.g. into the map).
Signed-off-by: Dan Vybiral <dan.vybiral@greencode.cz>