beta_49 hard-pinned s_ui_fscale=100 on the P4 to stop >100% chrome overflow, but
the "UI size" dropdown stays shown (CAP_LARGE_SCREEN), so changing it did nothing
(reported: "changing text size doesn't work on the P4"). Honour the saved pref like
the Tanmatsu does. Large/Huge can still make some P4 chrome that uses unscaled dims
tight; that's a per-screen SC() follow-up, not a reason to disable scaling.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
OTA_BIN_NAME only branched T-Deck vs everything-else -> wadamesh-heltec-v4-tft, so
V4-R8, ThinkNode M9, RAK Tap V2, both T-LoRa Pagers and Attaky would self-update by
downloading the V4-TFT bin -- flashing the wrong firmware onto the wrong board.
All are dual-slot OTA-capable (CAP_OTA=1 + app0/app1 partitions + FIRMWARE_OTA_ENV),
so map each to its own release bin. Pager splits on USE_LR1121. Tanmatsu stays out
(AppFS/launcher, CAP_OTA=0, never reaches this file).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The update-channel UI (the "Get test builds (beta)" toggle + the Install-update
button) is gated on a non-empty FIRMWARE_OTA_ENV. That is set per-env in
platformio.ini for the 8 S3 boards but was never set for the P4 IDF build, so the
whole update UI was hidden — P4 testers had no way to opt into beta and stayed
stuck reading the stable channel (the beta_49 "no update, 48 up to date" report).
- tdisplay_p4 CMakeLists: define FIRMWARE_OTA_ENV so the UI is shown.
- UITask.cpp: add a P4 branch to OTA_BIN_NAME so the download URL points at
wadamesh-tdisplay-p4[-lcd].bin, not the V4-TFT bin. AMOLED vs LCD split on
HAS_TDP4_LCD.
The C6 WiFi shim already routes HTTPClient/Update.h and the P4 partition table has
dual ota_0/ota_1 slots, so the OTA path itself was wired; this only unhides and
correctly targets it. Builds green on P4 (AMOLED) + V4-TFT. On-device toggle/OTA
test still pending (P4 dropped off USB, needs a power cycle).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Backspace tap now peeks the lock screen (reveal without unlocking),
mirroring the T-Deck's trackball-click behavior; holding Backspace still
fully unlocks.
- New native 480x222 lock wallpaper asset so the icon/wordmark aren't
crushed by the shared 320x240 art's cover-fill on this wide/short panel.
- Lock text forced to pure white and repositioned for this layout: clock
top-left over the icon, unread count + "Screen locked" stacked
bottom-right directly above the unlock hint.
- Fixed lock text flipping from white to black a moment after every reveal:
the lock overlay is CLICKABLE (to absorb taps), so the keyboard/encoder
nav rebuild was collecting it as a focus target -- navFocusCb's focus
highlight then reverse-videoed it, flipping every label's text to dark.
Tagged the overlay NAV_SKIP so it's never a nav target and never inverted.
- serviceLockscreen() now runs in the pager's loop branch too, so the lock
clock/unread badge keep refreshing after the first reveal instead of
freezing there.
All changes gated to TLORA_PAGER; no other board's behavior changes.
Signed-off-by: Tesso M Costa <tesso.martins@gmail.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>
- New Discover app: active NODE_DISCOVER sweep -> signal-ranked nearby list, tap-to-add-contact, GPS wardriving log (SD CSV) + signal-coloured coverage overlay on the map. Board-agnostic.
- T-Display P4 TFT-LCD (HI8561) variant support (WADA_P4_LCD build; AMOLED bin untouched).
- Wire the Attaky Core board (#158, @attakygit) into the release matrix + web flasher.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* attaky_mesh_series: add board variant with FT6636 touch and landscape UI
* attaky_mesh_series: P3 battery (MAX17048) + GPS + shared-I2C mutex
- AttakySharedI2C: one Wire.begin() + a mutex serializing the shared board
I2C bus (SDA=2/SCL=1) across the FT6636 touch poll (core 0), the MAX17048
fuel gauge (core 1), and the future AW9523 keyboard. Arduino's TwoWire
buffers are not concurrency-safe.
- board.begin() owns the authoritative bus init; the touch driver now takes
the lock around every transaction instead of its own Wire.begin().
- getBattMilliVolts(): real MAX17048 VCELL read (78.125 uV/LSB -> mV = raw*5/64),
2 s cache + lock, replacing the 4000 mV stub. Upstream maps mV -> % itself.
Hardware-verified ~4058 mV.
- GPS: rename the dead GPS_BAUD flag to GPS_BAUD_RATE (the core reads that; 9600
only worked via the #else default). Pins verified on hardware: ESP RX = IO17
receives ATGM336H NMEA; MeshCore's module-centric PIN_GPS_TX=17 maps correctly.
* attaky_mesh_series: Control_Keyboard_(Bottom) dual-AW9523 keyboard
Add the Control_Keyboard_(Bottom)_1.0 module as an ADDITIVE hardware
keyboard on the touch board. Two AW9523 expanders (0x5A left / 0x5B
right) each scan a 5x5 matrix over the shared board I2C bus, serialized
through the AttakySharedI2C mutex alongside the FT6636 touch poll and the
MAX17048 fuel gauge.
The driver emits final bytes (printable ASCII, backspace, enter, space)
with momentary Shift resolved to uppercase in-driver. UITask injects them
into the focused textarea exactly like the web-mirror keystroke path, so
CAP_KEYBOARD stays 0 and the on-screen keyboard is not suppressed (touch
typing does not regress). Focus-gated: the matrices are only scanned
while an editable field is bound, so a key has zero side effect when
nothing is focused.
Hardware-verified, both halves: q/w/e/r/t (0x5A) + y (0x5B), Shift
uppercase, backspace, space, enter; on-screen keyboard intact.
* attaky_mesh_series: use reliable 115200 upload speed
* attaky_mesh_series: use core default GPS baud
* attaky_mesh_series: restore GPS_BAUD setting
* attaky_mesh_series: rename PlatformIO environment
* pager: keep encoder focus inside the chat message list, remap composer nav
The rotary encoder's plain turn stepped LVGL's focus group directly, which
only ever contains the currently-materialized bubbles (the message list is
virtualized; loading more history is scroll-driven, not focus-driven). So
turning past the first/last *loaded* bubble escaped the list onto the
header/composer even with plenty of un-loaded history in that direction.
A turn at that edge now scrolls the list instead when more messages exist
(reusing the existing free-scroll helper), which triggers virtualization to
load the next batch; focus only leaves the list at the true oldest/newest
message. Fast turning folds into a pending target so it doesn't escape
mid-load, and the render's focus re-aim now runs synchronously so the
transient auto-focus LVGL assigns a deleted row's neighbor never paints.
Also removes the two touch-only jump-to-oldest/latest arrows on this board
(no touchscreen to tap them) and remaps the chat's key bindings now that
plain turns stay in the list:
- Orange/Alt tap: jump to the newest message + focus the composer (was
Backspace's job).
- Backspace tap: jump to the first unread message below the "NEW ----"
divider, or the newest message if nothing's unread.
- Composer row edges: Send+NEXT reaches the channel-settings gear (falls
back to the newest bubble in a DM, which has no gear); the quick-reply
chip+PREV goes straight to the newest bubble.
TLORA_PAGER_SHORTCUTS.md documents the new chat-screen behavior.
Signed-off-by: Tesso M Costa <tesso.martins@gmail.com>
* fix scroll
---------
Signed-off-by: Tesso M Costa <tesso.martins@gmail.com>
- Contact-loss fix (microSD devices): atomic temp+swap saveContacts, unified robust
boot SD-mount ladder, and a storage-truth line in the Store-on-SD setting.
- Heltec V4-R8: route MISO for the shared FSPI bus so the microSD mounts; wake-from-sleep
freeze fixed (panel sleep goes through LovyanGFX's own bus, not the HSPI shim).
- Do Not Disturb (originally PR #156 by Tesso Codemonkey / @codemonkeybr): scheduled
sound-silencing window in Settings > Sound + a status-bar bell.
- Control-center redesign: round icon-only chips that spread to fill each row (T-Deck 2x4,
V4 portrait 2 rows), frosted-glass panel, real lock glyph + dynamic bell/bell-slash DND
chip + keyboard icon+word chip (new cc_icons_16 FontAwesome font), per-chip confirmation
toasts, press-grow clip fix, and a V4-portrait status-bar BLE/clock overlap fix.
- LilyGo T-Pager: map tile cache falls back to the microSD under the launcher (fixes the
"storage error / reflash the tiles partition" dead-end).
Co-Authored-By: Tesso Codemonkey <798026+codemonkeybr@users.noreply.github.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Heltec V4-R8: the CHSC6x touch ran on a 2nd I2C controller (Wire1) bound to the
SAME pads (17/18) as the board Wire (RTC + sensors), driven from two cores — a
classic SDA-stuck-low wedge = the tester's random hard-freezes-needing-reset.
Touch now shares the one board Wire (V4CAP_WIRE selector), so arduino-esp32's
per-instance TwoWire mutex serialises all access, the proven T-Deck pattern.
- Wi-Fi settings: the join/details/hidden sheet floats on the base layer above the
settings page; navMaybeRebuild only recognised the settings sheet + chat + top
layer as focus roots, so on no-touch/keypad-nav boards (Tanmatsu) its SSID/
password fields + Join button were never collected into the focus group and
couldn't be selected. Add s_wifi_sheet as a nav focus root (priority over the
settings sheet beneath it).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Full keyboard+encoder-driven board port: TCA8418 QWERTY matrix, rotary encoder
focus-nav, ST7796 display, ES8311 notification sound, both radio variants.
Hardware-verified by the author against a live mesh.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
# Conflicts:
# src/ui-touch/UITask.cpp
Both floating jump arrows in the open-chat view are touch-only
affordances with no purpose on the T-LoRa Pager (no touchscreen), so
skip creating them on that board and leave the pointers null like
every other consumer already expects. The pager's existing
Backspace-tap "jump to latest" shortcut no longer depends on the
button object itself -- it checks chatVirtAwayFromBottom() directly
and calls chatVirtJumpToLatest(), the same virtualization-aware jump
the button's own click handler used.
Signed-off-by: Tesso M Costa <tesso.martins@gmail.com>
Alt+Shift previously did double duty: toggle Caps Lock while editing a
field, or jump Home otherwise. Splitting these apart -- Alt+Backspace is
now the dedicated Home-jump gesture, and unlike Alt+Shift it isn't
context-dependent: it works whether or not a field is being edited, so
it's reachable from anywhere without first backing out of what you're
typing. Alt+Shift outside a field is now a no-op instead.
PagerKeyboard.cpp suppresses a Backspace press entirely while Alt is
held (no '\b' ring-push, no hold-to-back/hold-to-unlock tracking) and
reports it as a one-shot chord instead, mirroring the existing Alt+Shift
chord plumbing. Also closes the accent/@-mention popups first, since
those live on lv_layer_top() outside the tab content the Home jump
switches away from.
Signed-off-by: Tesso M Costa <tesso.martins@gmail.com>
App tiles previously fell through to navFocusCb's generic white
reverse-video fill when focused via keyboard/encoder nav, instead of
the accent-tinted LV_STATE_PRESSED look they already show on a touch
press -- a solid white block over a small rounded icon chip reads as a
glitch, not a highlight, and looked inconsistent with the touch-driven
boards. Tiles now opt into the same accent tint for both states.
Signed-off-by: Tesso M Costa <tesso.martins@gmail.com>
Typing "@partial" in a composer popped up a touch-only contact list
(NAV_SKIP_FLAG cells, same as the accent-variant box before it got the
same treatment) -- on this board's no-touch input model there was no
way to actually pick a name from it. Mirrors the accent box's pager nav
(rotary encoder walks the highlighted cell, Enter/click confirms,
Backspace cancels without touching the typed text), but grabs the
encoder the instant the list appears rather than requiring an explicit
Fn+Space arm: unlike the accent box (which pops up after almost every
letter typed and must not steal focus from ongoing typing), the mention
list only shows once the user has deliberately typed "@partial" looking
for someone to pick, so there's no ongoing typing to protect.
Signed-off-by: Tesso M Costa <tesso.martins@gmail.com>
The Fn(Alt)+Shift chord previously toggled Caps Lock unconditionally,
even with no text field focused to see the change reflected in --
reported as surprising and purposeless outside of typing. PagerKeyboard
now only reports the chord (pagerKeyboardConsumeAltShiftChord()); the UI
decides the effect: Caps Lock toggle while actually editing a field,
otherwise a direct jump to the Home screen (mirrors the "ta" derivation
handleHwKey() already uses to tell bound-but-unfocused fields apart from
one actually being edited).
Signed-off-by: Tesso M Costa <tesso.martins@gmail.com>
A focused chat bubble already opened its Ack/Mention/Copy/Info/Block
action menu on Enter (navEnterBubble), but the encoder's short click
only sent a plain ENTER, which a bubble doesn't otherwise react to.
Mirrors handleHwKey()'s Enter branch so both inputs agree.
Signed-off-by: Tesso M Costa <tesso.martins@gmail.com>
The V4-R8 uses an ESP32-S3R8 (8 MB OCTAL PSRAM) which claims GPIO33-37, so
Heltec moved every control signal off them and the Expansion Kit V2 rebuilt the
display/touch/SD block onto a hardware-SPI bus:
Vext 36->40 (active-LOW) . GPS_EN 34->42 . LED 35->46 . ADC_Ctrl removed
TFT (LovyanGFX HW-SPI): MOSI=15 SCK=16 MISO=45 CS=47 DC=48 RST=21 BL=44
touch (CHSC6x, shares board I2C 17/18) . micro-SD (shared TFT SPI, CS=3)
The build defines HELTEC_LORA_V4_TFT too, so it reuses all the V4 touch-UI code;
HELTEC_LORA_V4_R8 gates only the deltas. 8 MB enables the on-device web browser
(CAP_WEB_BROWSER) and the SD card (CAP_SD). Also wires the board into the release
+ web-flasher pipeline (release.sh, gen-flasher-meta.py, the flasher picker).
UNTESTED — no V4-R8 hardware yet. Pin sources: Meshtastic heltec_v4_r8 variant,
Heltec V4-R8 pinmap, and the Expansion_board_V2.03 schematic. All three touch
envs build clean; V4/T-Deck unregressed. On-device TODO: CHSC6x shared-Wire on
17/18, SD_CS=3, and the V2 PAM8904 buzzer.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Reuses mapNudge() (previously Tanmatsu's Ctrl+Arrow-only helper, now
shared) so W/A/S/D pan the map north/west/south/east on this board's
touch-less Map tab. Frees up D from the slider-nudge binding, which
moves to Q/E so the two gestures don't collide when a slider (e.g. the
map zoom bar) is focused while WASD panning is in play.
Signed-off-by: Tesso M Costa <tesso.martins@gmail.com>
The accent-variant popup (issue #22) that appears above the field when
typing a letter with accented variants was touch-only -- its cells are
NAV_SKIP_FLAG, deliberately excluded from the keyboard/encoder focus
group. On the pager (no touch at all) this made the popup pure visual
noise: it would appear and just sit there unreachable.
Added a small parallel nav path scoped to this board: Fn (Alt) held +
Space jumps into the popup (instead of typing a space or the usual
double-tap-cycle-layout action); the rotary encoder then walks the
highlighted variant; the encoder's own click confirms (reusing the
existing accentBoxCellCb via a synthesized CLICKED event, rather than
duplicating its delete-base-letter + insert-variant logic); Backspace
cancels without deleting the base letter. Everything else is swallowed
while active so a stray keypress can't leak into the message mid-pick.
Focus never actually leaves the text field throughout -- the popup's
cells stay outside the normal nav group the whole time -- so nothing
extra is needed to "return" focus after a pick or cancel.
Signed-off-by: Tesso M Costa <tesso.martins@gmail.com>
New "Web browser" section in deploy/site/docs.html (and a TOC entry)
covering the on-device text browser and tappable chat links
("Open in web" / "Create QR"), with three new device screenshots
(app_web, app_web_links, app_web_qr). Existing screenshots unchanged.
Also render the reader page inline in the DOC_CAPTURE tour: the reader
paints from the UI-update poll, which doesn't run while the tour blocks
the loop, so the tour now drives the fetch-wait + render itself before
capturing the Web app.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The Shift/Caps key previously toggled Caps Lock on every bare press --
a deliberate design choice reused from a working reference build, but
not conventional keyboard behavior. Changed to: held alone, it's a
momentary Shift (uppercases letters typed while held, same as a real
Shift key); held Alt then a Shift press instead chords into a toggle
for persistent Caps Lock. kCapsPos renamed kShiftPos to match its new
primary role -- zero other call sites reference it, and s_caps itself
is unchanged (still just "is Caps Lock engaged", only the trigger
changed).
Alt (row2,col0) and Shift (row2,col8) share a matrix row, so holding
all three of Alt+Shift+O or Alt+Shift+L at once phantom-ghosts a
'q'/'a' (diode-less-matrix 3-key rectangle, no software fix possible)
-- documented in the matrix-legend comment; harmless in practice since
the intended gesture is hold-Alt-tap-Shift-release-both, not holding
all three down together.
Signed-off-by: Tesso M Costa <tesso.martins@gmail.com>
- New "Web" app: on-device HTTPS text browser (strip HTML to readable text,
tappable links, back/fwd/refresh, no proxy). Gated to 8 MB boards via
CAP_WEB_BROWSER; hidden on the 2 MB V4 (can't do the TLS handshake).
- Tappable URLs in chat -> Open in web (8 MB) / Create QR (all boards).
- Drawn vector globe icon for the Web tile.
- Fix: French AZERTY on-screen keyboard lost its spacebar/OK/kbd-switch row
(a stray "" mid-map terminated the LVGL button map early). Restored.
- V4 web fetch resilience (192->96->48->24 KB buffer fallback).
- Home signal box: "tap for details" cue back at the top.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- New "Remote & web app" docs section covering the browser control panel
(Chats / Contacts / Discovered / Settings / Terminal), Screen mirror (VNC)
and Remote UI, with a 7-shot phone gallery of the real web app.
- Refreshed all device screenshots via the DOC_CAPTURE tour (now also
captures the Remote screen) and added the MQTT bridge settings page.
- New automated web-capture harness (scripts/doc/web-demo/): extracts the
real page from firmware, serves it with mock data, screenshots via Playwright.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The browser control panel gains full Chats / Contacts / Discovered / Settings
tabs (search, sort, filter, per-contact actions, notifications + live refresh,
ACK/repeats, add-from-discovered, live radio settings, quick-command picker).
Heltec V4 map tiles: fixed the "only the top renders" bug on the 2 MB-PSRAM
board with a persistent tile-buffer pool + viewport culling (no more per-render
128 KB churn/fragmentation). Adds an opt-in "Tile debug overlay" (Map options).
Also: V4 physical-keyboard input in remote/VNC, clearer remote-access screen,
complete Terminal help on device + web, WADAMESH header + status icons.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Remote mode showed a black screen on 2 MB-PSRAM boards (Heltec V4): the
mirror ring/shadow were too big to allocate. Size the ring to one frame
and drop the remote render to 400x240 so the whole footprint is smaller
than VNC's (which already worked). Add a touch-and-hold (3 s) exit for
keyboard-less touch boards and an Exit button on the web page.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Stream the live LVGL UI to a phone/desktop browser over the WS companion
server (VNC app) plus a headless REMOTE mode that renders the UI at a
browser-friendly resolution. Non-blocking per-client TX buffering, a
core-0 stream task guarded by a recursive mutex, an on-page Rotate
orientation toggle, and a small 480x288 render upscaled in the browser.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Completes the microSD work from the previous commit: wavOpen()'s
"sd:"-prefixed path branch, fmIsAudio()/fmOpenAudio() (the .wav ->
notification-sound chooser opened from the File Manager), and the
popup-dismiss registry entry for it (so Back/Escape via the encoder or
keyboard closes the picker, not just the on-screen X) are all widened
the same way as the previous commit's browsing support -- specific
call sites only, CAP_SD/CAP_FILESYSTEM stay 0 in device_caps.h.
Also updates that file's now-stale CAP_SOUND_FILES comment, which said
the pager's WAV picker only ever reads from SPIFFS -- it can read from
the SD card now too.
Signed-off-by: Tesso M Costa <tesso.martins@gmail.com>
pagerKeyboardSetBacklight-style dead-hardware situation: the board-level
SD wiring (power rail, card-detect, chip-select parking, shared-SPI bus
reuse) was already done during earlier bring-up, but nothing ever called
SD.begin(), so the slot was unusable. CAP_SD/CAP_FILESYSTEM already
existed for exactly this in device_caps.h, left at 0 with a "pager needs
its own SD wiring" comment.
CAP_SD turns out to gate ~37 call sites across UITask.cpp -- file
manager, WAV picker, DataStore contacts/channels routing, map-tile
fallback, wallpaper scanning, settings backup, screenshots. Scope for
this pass is file-manager browsing only (WAV picker follow-up commit);
flipping the top-level CAP_SD/CAP_FILESYSTEM macros would have silently
turned on all ~37 at once, several referencing T-Deck-only helpers never
vetted for this board. So device_caps.h is untouched -- CAP_SD/
CAP_FILESYSTEM stay 0 for the pager -- and only the specific call sites
needed for browsing are widened with `|| defined(TLORA_PAGER)`.
- fmSdTryMount()/fmSdUnmount()/fmSdClickCb(): widened, plus a pager-only
sdSharedSPI() (reuses TFT_eSPI::getSPIinstance(), the same shared bus
the radio already uses -- variants/lilygo_tlora_pager/target.cpp) and
a card-detect pre-check via the XL9555 expander (PAGER_EXPAND_SD_DET,
inverted polarity confirmed against LilyGoLib's own installSD()).
Format support is explicitly out of scope -- the pager's SD row is
tap-to-open only, no long-press-to-format.
- fmShowRoots(): pager SD row, shown only when a card is actually
detected (no T-Deck-style "tap to mount" fallback -- the detect line
makes "not present" unambiguous).
- fmIsSd(), the CAP_FILESYSTEM-gated Apps-drawer "Files" entry (dispatch
+ grid tile), and the insert/remove poll: widened the same way.
All ~30 other CAP_SD/CAP_FILESYSTEM sites (DataStore SD routing, tile
fallback, wallpaper, telemetry, settings backup, screenshots) are
untouched and stay exactly as unavailable on the pager as before.
Signed-off-by: Tesso M Costa <tesso.martins@gmail.com>
pagerKeyboardSetBacklight() existed but nothing called it. The mode
state, NVS persistence, and boot restore were already generic across
every CAP_KEYBOARD board (s_kb_bl_mode, touchPrefsGetKbBacklight()),
so only two pieces were missing for this board:
- updatePagerKbBacklight(): a per-tick apply step, the pager's
analog of the T-Deck's inline backlight calc / Tanmatsu's
tanKbBacklightTick(). No brightness curve needed here (the pager's
backlight is a plain LEDC PWM strip, not a dimmable slider like the
T-Deck's) -- on/auto-active drive full duty (255), everything else
(off, idle, screen-off, locked) drives 0. Runs unconditionally in
the pager's loop branch so it can force the light dark the instant
the screen goes off/locked.
- A "Keyboard backlight" row in Settings -> Keyboard (pager-only,
sibling to the T-Deck's slider block) that cycles off/on/auto,
mirroring the Control Center chip's semantics -- the Control Center
itself is unreachable on this board (its launcher is touch-only).
Also fixed auto mode actually responding to input: neither
handleHwKey() nor updatePagerEncoder() called noteKbActivity() (the
function that stamps the auto-mode idle timer) -- they only called
the separate noteUserInput() (the screen idle-dim timer), so auto
mode would light briefly at boot and then never again regardless of
typing or turning the encoder. Both now stamp it too.
Signed-off-by: Tesso M Costa <tesso.martins@gmail.com>
Fixes:
- Card-less devices (Heltec V4) froze for seconds: a full contacts rewrite to
SPIFFS on every advert refresh triggered a multi-second GC in the mesh loop.
Coalesce advert-driven saves (a contact-set change still saves promptly); SD
boards unchanged. Flush on reboot/power-off so nothing is lost.
- Long translations (FR/DE/...) overflowed fixed-width labels: new
uiFitLabelWidth() shrinks the font to fit one line instead of clipping/wrapping
- applied to the message menu, confirm dialogs, and app-drawer tiles.
- Home signal graph: "tap for details" hint moved to bottom-right so it no longer
overlaps the "Sig" chip in longer languages.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Persistence (Leon P / MrWeavis, beta_38):
- Deleting a message or clearing a chat now persists synchronously, so it
survives an immediate hardware reset instead of "coming back" (the tombstone
used to ride the 30 s lazy SD flush, which a reset beats).
- The history file is now written compactly — only the used records,
oldest-first, instead of all ~5000 ring slots every time. A typical chat
writes ~48 KB instead of ~1.2 MB, so the synchronous delete/clear/reboot
flush no longer hitches the UI (and normal message flushes are lighter too).
Old history files still load (the reader derives its slot count from size).
Emoji picker:
- Baked the 14 glyphs that were listed but never in the font (umbrella, coffee,
pizza, cake, rocket, camera, phone, laptop, calendar, antenna, e-mail, house,
car, watch); they rendered as the notdef box.
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>
The P4's internal FFat 'locfd' has a broken FAT metadata layer: open/
read/write work but f_stat/exists()/size() return garbage - and WHICH
garbage shifts with the build. At -Og the exists()-gated identity and
NodePrefs loads worked by luck; the -Os switch flipped the lie and the
device booted with a fresh identity and default name, and profile
changes never survived a reboot (the loads failed, not the saves).
Chat history was unaffected because it already lives on SD_MMC.
- DataStore::useSdMmcStorage(): full-store adoption of the card
(/meshcomod root, identity store included), mirroring the T-Deck's
useSdStorage(). The old card-root contacts3/channels2/adv_blobs from
the secondary-FS era are renamed under /meshcomod.
- Tanmatsu boot: one-time rescue of identity + prefs off FFat by OPEN
and READ probing (never exists()/size() on that FS), then adopt the
card. FFat remains the no-card fallback only.
- DataStore::loadPrefs() probes candidates by opening and reading a
byte instead of exists() - truthful on the P4, identical elsewhere.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Since the beta_35 lazy history writer, message-ring flushes are spaced
up to 30 s apart on the deep SD ring. Every on-device reboot/power path
compensates by calling persistHistoryNow() first, but the companion
protocol's CMD_REBOOT (the phone app's reboot button) went straight to
board.reboot(), dropping everything since the last flush - the newest,
just-read messages, which reads as 'my channel and DM messages get
deleted after a manual reboot' (Leon P, T-Deck under Launcher, beta_35
through beta_37).
persistHistoryNow() is now an AbstractUITask hook (default no-op, so
non-touch companions are unchanged) and CMD_REBOOT calls it before
rebooting, matching the power-menu contract.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Users who flipped 'Store data on SD' before beta_36 ran with the toggle
IGNORED (the flag never survived a reboot), so their identity, prefs and
contacts kept living on SPIFFS. When beta_36 made the flag finally take
effect, useSdStorage() pointed the store at an EMPTY card, the identity
store minted a brand-new node, and the user 'lost' their profile. The
data was never gone - it was orphaned on internal flash.
Two-part fix:
- Boot: adopting the card while SPIFFS holds an identity the card lacks
now copies every SPIFFS file into SD:/meshcomod first (kv files
flatten to the card's SdNvsPrefs layout). If the identity file fails
to land, the device stays on SPIFFS for the boot instead of adopting
a card without it.
- Settings -> Storage gains 'Copy internal data to SD' for the users
already bitten (their card now holds a fresh identity the boot-time
no-clobber gate rightly won't touch): confirmation, overwrite-copy of
everything from internal flash, force the SD pref on, reboot.
Workaround until this ships: toggle 'Store data on SD' OFF and reboot -
the device reads internal flash again and the old profile returns.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
With Delete the menu carries up to 7 actions; a single column no longer
fit the 240-px screens without scrolling. Two per row (wider 200-px card,
odd last button on its own row) keeps the whole menu on one screen.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- The chat jump buttons are bare arrows flush against the right edge now
(28x36 transparent touch target, accent-colored glyph) instead of 40 px
filled circles; they run at full opacity while the list scrolls and dim
to 50 percent one second after the last scroll activity. The Tanmatsu
keeps its F-key chip styling and geometry.
- The long-press message menu gains a Delete row: the message is
tombstoned in the ring (thread tag blanked, so every walker skips the
slot and live ring indexes stay valid) and both the chat and the thread
list refresh.
- The chat/channel settings sheet (in-chat gear and list long-press)
gains a 'Delete history' row for both DMs and channels: confirmation
prompt, then every message of the thread is tombstoned. The thread row,
channel membership and contact stay; only the conversation is wiped.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- chatVirtReset no longer queues lv_obj_del_async on the divider: the
empty-thread paths lv_obj_clean two lines later freed it first and the
deferred lv_obj_del then ran on freed memory (confirmed UAF, plus a
cross-panel variant when both panels are open). Downstream cleans own
the widget; the reset only nulls the pointer now.
- Compact rows register bubbleLongPressMenuCb for LV_EVENT_LONG_PRESSED
(the only event the callback acts on) instead of PRESSED/RELEASED/
PRESS_LOST/DELETE, all of which it filtered out - the per-message menu
was completely dead in compact mode.
- Bubble mode gets tap-to-resend back on failed sends (CLICKED handler
plus the 'tap to resend' footer), matching the compact path and the
pre-virtualization behavior.
- need_layout gains a content-generation guard (endpoint ring slots): at
ring capacity n stays constant while content rotates, and equal-count
thread switches reused stale offsets - rows rendered the next message's
text at the previous message's position. The dead dirty_timeline rescue
(cleared before its async consumer could read it) is removed.
- New chatVirtTryAppendLayout fast path: pure appends extend offsets for
the new tail instead of re-measuring the whole thread (the full measure
is ~100 ms of UI stall per received message on a 5000-message SD ring).
- lv_async_call failure no longer wedges the render busy flag; the nav
focus fallback validates s_nav_objs[0] before focusing it (bubbles now
recycle on scroll-end, so slot 0 can be one tick stale); dead
chatVirtClearAllChildren/DeleteMsgsChild pair removed; stray
FIRMWARE_BUILD_DATE bump reverted.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>