Commit Graph
270 Commits
Author SHA1 Message Date
Tesso M Costa df7f9bc2ab pager: fix encoder direction, and Alt+turn free-scroll for nested screens
Encoder direction was backwards on hardware (confirmed by user testing):
kQuadTable's CW/CCW sign didn't match this physical part's wiring, so
turning the way you'd expect to move forward/down moved focus and
scroll backward/up instead. Negated once in pagerEncoderReadDelta()
so every consumer (nav NEXT/PREV, Alt+turn tab-switch, Alt+turn page
scroll) inherits the corrected sense from one place.

Also scope the pager's Alt+encoder gesture: it used to always jump
between main tabs, even from inside a settings sheet or chat, silently
abandoning it. Now Alt+turn only tab-switches at the main-tab level
(navOnMainPage()); inside a nested scrollable screen it instead
free-scrolls the page like a touch drag (navScrollFocused()'s ANIMATED
lv_obj_scroll_by, unlike navFocusCb's one-shot LV_ANIM_OFF
scroll_to_view, reliably repaints on this board), then snaps focus to
whatever's now nearest the top of the visible viewport
(navRefocusFirstVisible()) so plain NEXT/PREV resumes from a visible
spot instead of a focus stuck off-glass.

Signed-off-by: Tesso M Costa <tesso.martins@gmail.com>
2026-07-07 16:52:44 -06:00
Tesso M Costa b3c638842e pager: fix LR1121 TX power default and RX-boosted-gain gating
Boot TX power was left at a conservative 10dBm (Heltec V4's pattern) with
no way to raise it, unlike the T-Deck which boots straight at the chip's
real 22dBm ceiling. On hardware this was silently degrading outbound
range: adverts/DMs sent from the pager were unreliable while inbound
reception was fine, since the far node's own TX power was unaffected.
22dBm is confirmed as this chip's actual sub-GHz HP-PA ceiling via
RadioLib's LR1120::checkOutputPower() (LR1121 inherits it) and matches
trail-mate's own working config for this board.

Also widen the three RX-boosted-gain gates in MyMesh.cpp/DataStore.cpp
that only checked USE_SX1262/USE_SX1268 to include USE_LR1121 --
CustomLR1121Wrapper already implements setRxBoostedGainMode/
getRxBoostedGainMode, so the setting was silently a no-op for this radio.

Signed-off-by: Tesso M Costa <tesso.martins@gmail.com>
2026-07-07 15:50:07 -06:00
Tesso M Costa 05b24f01ad pager: fix encoder wake-selects-Skip, and confirm-before-advance on region pick
Two related first-boot-wizard bugs found on hardware:

- updatePagerEncoder() woke the screen on either a turn OR a bare click.
  Waking via click let the SAME press-release cycle fall straight into
  the short-click logic further down as an ordinary click on whatever was
  already focused -- concretely, it selected "Skip" on the wizard's
  welcome screen the instant the screen lit up. Only a turn wakes the
  screen now; BOOT is unchanged and still wakes via its own path.

- The region step's 20-preset list auto-jumped focus to Next on the
  FIRST click, before the row's colour change was even visible -- no way
  to confirm the right region got picked before focus moved off the
  list. Changed to two-step: the first click on a row selects/recolours
  it and focus stays put; a second click on the row that's ALREADY
  selected is treated as the explicit confirm and jumps focus to Next.

Both gated CAP_KEYPAD_NAV (Tanmatsu + pager); T-Deck/Heltec V4 touch
flows are unaffected either way (moving the internal group focus is
invisible without active keypad-nav).

User-verified fixed on hardware (fresh first-boot flash, region select,
Wi-Fi connect, and map tile auto-download all working).

Signed-off-by: Tesso M Costa <tesso.martins@gmail.com>
2026-07-07 14:16:21 -06:00
Tesso M Costa f61b8fbbcf pager: Alt+rotary jumps between main tabs
The bottom tab bar (Chats/Contacts/Home/Map/Settings) is deliberately not
a nav-group focus target, same as T-Deck/Tanmatsu -- but those boards
reach it via their own dedicated hotkeys (programmable keys / coloured
shape keys), which the pager has none of. Plain rotary turning could
therefore only ever move focus within the current screen; the tab bar
icons were unreachable from Home (or anywhere else).

Exposed pagerKeyboardAltHeld() (raw modifier state already tracked by
pagerKeyboardPoll() to drive the symbol layer, just never surfaced
before) and used it in updatePagerEncoder(): holding Alt (the bottom-left
orange key) while turning the encoder now calls navSwitchTab(+-1) per
detent instead of the normal focus-move NEXT/PREV, cycling through the
5 main tabs directly. Alt alone still types nothing, so this doesn't
take anything away from the keyboard.

User-verified fixed on hardware.

Signed-off-by: Tesso M Costa <tesso.martins@gmail.com>
2026-07-07 11:21:57 -06:00
Tesso M Costa fe4ed029f5 pager: make rotary long-press a real universal back gesture
Two gaps left "hold to go back" not actually working anywhere but inside
a popup:

- The popup registry (anyPopupOpen()/hwKeyDismissTopPopup(), which drives
  the actual dismiss) only recognized HAS_TDECK_GT911/HAS_TANMATSU, so it
  never knew how to close the pager's own Terminal fullscreen view or its
  command picker -- the encoder's long-press was sending a raw Esc key
  that nothing handled. Widened to TLORA_PAGER.

- Even with that fixed, a BARE main tab (Chats, Contacts, Map, Settings)
  is neither a popup nor an open chat/channel detail, so the back cascade
  fell through to a no-op Esc. T-Deck/Tanmatsu don't hit this because they
  have separate dedicated Home hotkeys; the pager has none (no
  programmable keys, and the bottom tab bar is deliberately not a
  nav-group target). Added a third rung: fall back to the Home tab before
  plain Esc, matching every other "close" path in this app (the
  Terminal/Files Home button, the fullscreen-view popup closer) already
  landing on Home rather than a real back-stack.

User-verified fixed on hardware.

Signed-off-by: Tesso M Costa <tesso.martins@gmail.com>
2026-07-07 11:12:18 -06:00
Tesso M Costa 53251c33e0 pager: reset idle timer on keyboard and encoder activity
Neither input path called noteUserInput() for the common no-field-focused
case, so the idle timeout kept counting down during active use:

- handleHwKey()'s no-textarea branch only calls noteUserInput() when
  dismissing a popup or matching a tab hotkey. tabForKey() no longer maps
  any letters (tab jumps moved to the trackball-only programmable
  hotkeys) and isDismissKey() always returns false for the pager's full
  QWERTY, so with no field focused, every keypress silently no-op'd here.

- updatePagerEncoder() only called noteUserInput() to wake an already-off
  screen; turning or clicking the encoder while the screen was ON never
  reset the timer.

This board has no touch and no trackball to fall back on for idle-reset,
so both gaps were fatal: the screen dimmed after ~30s of real use.

User-verified fixed on hardware.

Signed-off-by: Tesso M Costa <tesso.martins@gmail.com>
2026-07-07 10:45:17 -06:00
Tesso M Costa f9c3db99c2 ui: scope keypad/trackball nav to the frontmost top-layer popup
navMaybeRebuild() re-roots keyboard/rotary focus onto lv_layer_top() as
soon as ANY child is visible there, but collected the WHOLE top layer.
Fullscreen views that spawn their own popups (Terminal's command picker,
file manager prompts/action sheets) add those popups as separate
lv_layer_top() siblings rather than nesting them in the view, so with two
visible top-layer children at once, nav walked the hidden view underneath
before ever reaching the popup on top of it (reported: opening the
terminal's command picker left rotary focus on the terminal behind it).

LVGL has no z-index — child order is stacking order — so the last
visible, non-NAV_SKIP child is the one actually on top. Added
navTopFrontmostChild() and root nav on it instead of the whole top layer.
Single-popup screens are unaffected (same one child either way).

Not board-gated: this is a general nav-focus correctness fix that
benefits any board using keypad/trackball navigation, not pager-specific.

User-verified fixed on hardware.

Signed-off-by: Tesso M Costa <tesso.martins@gmail.com>
2026-07-07 10:34:59 -06:00
Tesso M Costa 230b0936bc pager: fix terminal Enter-to-submit and always-on rotary focus highlight
Two related non-touch navigation bugs on the pager's Terminal screen:

- handleHwKey()'s ENTER handling only routed to terminalSubmit() under
  HAS_TDECK_GT911, so physical-keyboard Enter fell through to hideKb()
  instead of running the typed command. Widened to TLORA_PAGER.

- s_nav_show (whether the amber keypad-focus ring is painted) only
  defaulted true for HAS_TANMATSU ("keyboard-only device: focus highlight
  is always visible"); every other board, including the pager, defaulted
  false and only flips true inside CAP_TRACKBALL-gated code, which never
  runs on the pager (no trackball). Since the pager also has no touch,
  rotary-driven focus was moving but never rendered, so the terminal's
  send button looked unselectable. Widened to TLORA_PAGER, matching the
  Tanmatsu's rationale exactly.

User-verified fixed on hardware.

Signed-off-by: Tesso M Costa <tesso.martins@gmail.com>
2026-07-07 10:25:10 -06:00
Tesso M Costa d4ca18335e pager: keep home TX/RX chart clear of the right-hand button column
makeHome()'s chart_w gate only widened the chart's cleared strip for
HAS_TDECK_GT911/HAS_TANMATSU, so the pager (landscape, same right-hand
Advert/Terminal/Apps/Control column) fell into the full-width else branch
and the chart border/Sig chip ran under the buttons. Add TLORA_PAGER to
the gate, matching the other landscape boards.

User-verified fixed on hardware.

Signed-off-by: Tesso M Costa <tesso.martins@gmail.com>
2026-07-07 10:10:00 -06:00
Tesso M Costa 78a6c01f95 pager: fix M7 hardware bring-up bugs, verify UI/keyboard/encoder on device
Six real bugs found and fixed across two hardware sessions (full account in
TLORA_PAGER_M7_HW_DEBUG_LOG.md): stale-NVS BLE bond crash, no screen-wake
path for keyboard/encoder input, wrong TFT_eSPI SPI-port index on ESP32-S3,
missing panel INVON for this ST7796 batch, two SPI hosts fighting over the
shared radio/display pins, and a floating XL9555 ch6 panel hardware-reset
line (the actual root cause of the intermittent black screen, not the SPI
race originally suspected). TLoraPagerBoard::begin() now drives the full
vendor rail set including the display reset pulse and parks shared-SPI
selects high before first bus traffic.

Boot logo, full LVGL UI, QWERTY keyboard navigation, and rotary encoder
nav/select are now verified working on real hardware. Trackers updated:
Milestone 7's UI portion is done; the remaining radio/USB-companion/SD
gates move to a new Milestone 7b so they don't block Milestone 8's
on-device UI pass, which is now active.

All three envs (Heltec V4, T-Deck, pager) build green.

Signed-off-by: Tesso M Costa <tesso.martins@gmail.com>
2026-07-07 09:44:36 -06:00
Tesso M Costa 641700fad0 touch: wire T-LoRa Pager into UITask (M6)
Adds the pager as a first-class non-touch UI target: new device_caps.h
block (no touch, hw keyboard + rotary-encoder nav, 480x222, GPS/OTA/lock
screen; SD/filesystem left off since the mount code is still hardcoded to
HAS_TDECK_GT911, not genuinely CAP_SD-generic), KEYPAD indev registration
reusing the Tanmatsu keypad plumbing, forced 480x222 landscape rotation,
keyboard/encoder input drain in the main loop, and AW9364-correct
brightness wiring ahead of the existing PWM backlight branch.

Also fixes two latent gaps this surfaced: navMaybeRebuild() was
unreachable for the pager's cap combination (would have left the focus
group permanently empty), and several HAS_TDECK_KEYBOARD-gated helpers
needed their reopened regions individually widened rather than the file
touched wholesale.

All three envs build green; Heltec V4 TFT and T-Deck binaries are
byte-identical to pre-change (25.3%/73.5%), confirming every edit stayed
behind the pager gate.

Signed-off-by: Tesso M Costa <tesso.martins@gmail.com>
2026-07-06 19:06:31 -06:00
Tesso M Costa 3e71867e71 pager: add TCA8418 keyboard + rotary encoder drivers
PagerKeyboard.{h,cpp}: TCA8418 matrix keyboard over I2C. Unlike the
T-Deck's keyboard (a second MCU that resolves ASCII itself), the
TCA8418 only reports raw row/col events, so the keymap and
shift/sym/alt state machine live here -- keymap tables cribbed from
trail-mate's LilyGoKeyboard (same physical PCB). Press/release
polarity verified against the TCA8418 datasheet directly, since it
disagrees with the Adafruit library's own header comment. Backlight is
direct LEDC PWM on GPIO 46, using this framework version's actual
channel-based API (confirmed before writing the call, not assumed).

PagerEncoder.{h,cpp}: true A/B quadrature decode via a standard
Gray-code transition table, ISR-cheap-arithmetic-only like
TDeckTrackball's pattern (that driver's 4 discrete direction pins
aren't actually quadrature, so its exact shape doesn't transfer, only
the ISR/snapshot-read style does). Detent scaling (4 raw transitions
per detent) is an unverified assumption pending hardware bring-up.

Both gated on HAS_PAGER_KEYBOARD/HAS_PAGER_ENCODER, already reached by
every env's existing helpers/input build filter. All three envs build
green.

Signed-off-by: Tesso M Costa <tesso.martins@gmail.com>
2026-07-06 16:35:11 -06:00
Tesso M Costa ae690e3fbb pager: add platformio.ini env (first full compile)
[env:tlora_pager_lr1121_companion_radio_touch], cloned from the T-Deck
env with board/radio/display/GPS deltas for the LR1121 + ST7796 pager.

Two structural fixes were needed to actually get this to compile:

- pins_arduino.h moves to its own variants/lilygo_tlora_pager_pins/
  folder (board JSON's "variant" field updated to match). PlatformIO's
  arduino-esp32 build script unconditionally compiles every source
  file under board_build.variants_dir/<board.variant>/ as a separate
  framework-level library with none of our own lib_deps -- since this
  board (unlike T-Deck/Heltec, which point at framework-bundled
  variants) has no framework-bundled variant of its own, our
  variants_dir override was colliding with our own board-glue files
  and failing to find Wire.h/RadioLib.h.

- CustomLR1121Wrapper.h's bare quoted includes of RadioLibWrappers.h/
  LR11x0Reset.h only resolve for core-lib files living in the same
  directory as their targets; switched to angle-bracket includes with
  the full core-lib subpath.

Also required one small, forced addition to UITask.cpp: its display
class selection only recognized the Tanmatsu vs. everything-else split,
which made this env's own hard compile gate unreachable. Added a single
#elif arm for the pager's display type: no other UITask.cpp behavior
changes, indev/resolution/layout wiring is still a later milestone.

All three envs (Heltec V4, T-Deck, pager) build green.

Signed-off-by: Tesso M Costa <tesso.martins@gmail.com>
2026-07-06 16:14:00 -06:00
Tesso M Costa 546cc9079a pager: add ST7796 display driver + AW9364 backlight (src/helpers/ui)
ST7796LCDDisplay implements the core's DisplayDriver interface on
TFT_eSPI for the pager's 480x222 landscape panel -- the first real
TFT_eSPI consumer in this codebase, since ST7789LCDDisplay (the sibling
this was modeled on) turns out to be Adafruit_GFX-based for both
Heltec V4 and T-Deck. Every TFT_eSPI call was verified against the
pinned library source rather than assumed.

Backlight is the AW9364 stepped pulse-dimmer, wrapped directly in the
display class via SensorLib's AW9364LedDriver (already a dependency)
rather than a hand-rolled pulse timer, exposing a pct-based
setBrightness()/getBrightness() hook for a later UITask.cpp wiring
pass.

Also adds a compile-time guard for CGRAM_OFFSET: this panel's 222px
glass is narrower than the ST7796 controller's native GRAM, and
TFT_eSPI only applies the required coordinate offset when that flag is
set. Without the guard, a missing flag would build clean but silently
shift every frame by 49px.

Nothing references these files yet, so the shipping T-Deck/Heltec V4
envs are unaffected.

Signed-off-by: Tesso M Costa <tesso.martins@gmail.com>
2026-07-06 15:10:18 -06:00
Tesso M Costa 9d5df3001d pager: add port tracker + milestone playbook docs
TLORA_PAGER_PORT.md is the living status/decisions/risks tracker for
the T-LoRa Pager port; TLORA_PAGER_PORT_MILESTONES.md breaks the work
into ordered, agent-executable milestones with gates. Kept off main
until the port is further along.

Signed-off-by: Tesso M Costa <tesso.martins@gmail.com>
2026-07-06 14:24:33 -06:00
Tesso M Costa 82b6d3b56d pager: land LR1121 radio glue (target.h/.cpp + CustomLR1121 wrapper)
CustomLR1121{,Wrapper}.h are adapted from the core fork's CustomLR1110
pair (upstream MeshCore has no LR1121 support yet to crib instead).
target.cpp's radio_init() spells out the LR1121 bring-up sequence
directly since there's no std_init() helper for this chip: begin()
with the NodePrefs-driven params and TCXO voltage passed straight in,
the DIO5/DIO6 RF-switch table, and a CRC-length override to match the
other radio wrappers' wire-protocol framing. Nothing references these
files yet, so the shipping T-Deck/Heltec V4 envs are unaffected.

Signed-off-by: Tesso M Costa <tesso.martins@gmail.com>
2026-07-06 14:22:51 -06:00
Tesso M Costa be1996ce77 pager: land T-LoRa Pager variant skeleton (board JSON + board class)
Board class, pins, and partition table for the LilyGo T-LoRa Pager
(ESP32-S3, LR1121 radio variant), modeled on the T-Deck's shape.
TLoraPagerBoard brings up the XL9555 power-rail expander and BQ27220
fuel gauge over I2C (this board has no VBAT ADC divider). Nothing
references these files yet, so the shipping T-Deck/Heltec V4 envs are
unaffected; radio/display/env wiring land in follow-up commits.

Signed-off-by: Tesso M Costa <tesso.martins@gmail.com>
2026-07-06 13:43:24 -06:00
Kaj SchittecatandClaude Fable 5 01900296f3 touch: beta_35 — worker-thread history flush, radio memory guards, 30.5 KB DRAM freed, crash-safe prefs
- History flush runs on the core-0 worker (snapshot + storage-busy gate +
  bounded shutdown wait): kills the V4's multi-second ui:hist stalls and
  the refuses-to-wake-after-message symptom (SPIFFS GC off the UI thread).
- BLE + Wi-Fi runtime enables share the boot co-init heap guard (50 KB
  free + 20 KB block): refuse with a toast + revert the switch instead of
  panicking (BLE) or silently not starting while claiming on (Wi-Fi).
- Internal DRAM: static footprint 95,365 -> 64,908 B (30.5 KB freed):
  serial_interface -> PSRAM; 23 keyboard layout maps const'd -> flash;
  nine rings/tables -> psAlloc PSRAM (UITask/TouchPrefsStore/MyMesh);
  unused TinyUSB MSC + DFU-mode class drivers stubbed out of the link
  (usb_unused_class_stubs.c) — CDC + runtime-DFU untouched, double-flash
  verified over the stubbed stack.
- DataStore prefs writes are crash-safe (write .tmp, swap) with a
  self-healing loader (.tmp recovery) and a truthful save result up to
  the profile-name toast — fixes the boots-with-default-name-once report.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
beta_35
2026-07-06 13:34:24 +02:00
Kaj SchittecatandClaude Fable 5 4e1ac2ca6b touch: beta_34 — buffered LoRa receive ON by default (test channel) + core pin core-v1.16.5
Consumes the new core (buffered receive drain task + RX counters, #13
stock payload layout, GPS-date guard). Fork side:
- TouchPrefs v33 rx_queue, DEFAULT ON for the test channel (fresh
  installs + a one-time migrate for upgraders); opt-out toggle
  'Buffered receive (experimental)' in Radio & Mesh, applies live.
- About live tier shows LoRa RX heard/read/err/late-lost/qdrop, the
  field evidence for the missed-messages fix.
- Spectrum parks/resumes the drain task around raw radio use; MyMesh
  setParams sequences hold the radio mutex.
- TanmatsuLoraRadio: no-op API stubs (P4 keeps its own RX path).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
beta_34
2026-07-06 11:15:52 +02:00
Kaj Schittecat 5d9a184314 chore: refresh Mesh America catalog for beta_33 2026-07-05 23:50:32 +02:00
Kaj SchittecatandClaude Fable 5 8e17b1b071 touch: beta_33 — async MQTT connect (freeze fix) + About sketch-size cache + favorites-full toast
- MqttBridge: reconnect() (blocking DNS + TCP + CONNACK, 2-10 s vs an
  unreachable broker, every 15 s) now runs on a one-shot core-0 task;
  _connecting hands the client between threads. Broker socket writes get
  the same zero-timeout writability probe as the TCP/WS companion
  servers, so a wedged broker fails a publish instead of stalling loop().
- About: ESP.getSketchSize()/getFreeSketchSpace() esp_image_verify-walk
  the whole ~3 MB image (~190 ms each); the 1 Hz refresh cost ~390 ms/s
  and flooded the stall ring (the ui:sbar entries field testers
  photographed). Computed once and cached.
- Favorites: adding past the 16-cap now toasts 'Favorites full (max 16)'
  instead of the wrong 'Removed from favorites'.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
beta_33
2026-07-05 23:34:22 +02:00
Kaj SchittecatandClaude Fable 5 b58cecab5c touch: beta_32 — non-blocking TCP/WS companion writes (Wi-Fi freeze fix) + chunked history flush + stall diagnostics
Root cause of the beta_31 periodic 1-2s freezes with Wi-Fi on: every
received mesh packet pushes an RX-log frame to all connected TCP/WS
companion clients from the loop thread, and writeAllBytes called
WiFiClient::write() unguarded — against a stale peer (phone asleep,
half-open socket) that blocks in the framework's 1s select() retries.
The 120ms app timeout only bounded attempts, not the first call.

- TCP+WS servers: probe writability (zero-timeout select on the client
  fd) before every write; lwIP reports writable only with ~2.8KB free,
  a whole frame is ~180B, so probed writes complete instantly.
- Reap clients whose writes fail continuously for 10s (half-open
  sockets previously stayed "connected" forever, eating the write
  budget on every pushed frame).
- Chat-history ring: chunked writer + wider flush spacing (same
  treatment saveContacts got in beta_30).
- Stall tracer: 16-entry ring of >=0.2s loop stalls, shown in About.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
beta_32
2026-07-05 22:55:42 +02:00
Kaj Schittecat 816045bb6b chore: refresh Mesh America catalog for beta_31 2026-07-05 21:13:27 +02:00
Kaj SchittecatandClaude Fable 5 11939a744e touch: beta_31 — WhatsApp-style chats tab (avatars + previews) with per-channel custom icons
- Default chat list: 56px rows with a round initials avatar in the thread's
  FNV-hue signature colour, last-message preview (getThreadLastMessage ring
  accessor), time top-right, unread pill + @ below; compact chat mode keeps
  the dense 34px contact-style rows (layout mode mixed into the rebuild sig)
- Per-channel avatar emoji: 'Chat icon' in the thread-settings sheet (tap =
  emoji sheet in new pick mode, long-press = reset to letters); persisted in
  a chmute-style keyed blob (touchPrefsGet/SetChannelEmoji, 24 x 48B)
- Fix: name/preview overlap (LONG_DOT labels need fixed one-line heights)
- Thread-sheet grid gap 6->4 so the 7th channel cell keeps one screen on T-Deck

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
beta_31
2026-07-05 21:07:42 +02:00
Kaj SchittecatandClaude Fable 5 f47d71a05b touch: beta_30 — big-mesh contacts perf (buffered save, in-place age labels, coalesced rebuilds) + live Spectrum
- DataStore::saveContacts packs byte-identical 152B records into ~5KB chunk
  writes (was ~12 tiny FS writes per contact: 2.6-3.0s at ~570 contacts on the
  loop thread — the #82 sluggishness); falls back to per-field writes on alloc failure
- Contacts 60s age tick updates the Heard labels in place (was a ~1.3s full
  teardown+rebuild); advert-driven rebuilds coalesced to one per 2.5s
- Spectrum: rolling waterfall row + live trace every quarter sweep (~250ms),
  8-bin/8ms sweep chunks (~20% faster full sweep); trace Y range now tracks the
  live min/max — instant expand, 3dB/sweep contract, 30dB minimum span

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
beta_30
2026-07-05 13:08:40 +02:00
Kaj SchittecatandClaude Fable 5 8eb63a7edd touch: beta_29 — clock floor for room-server replay guards, BLE repeats-heard, deep SD history, one-screen popups
- ClockFloorRTC on all boards: persisted monotonic send-timestamp floor (touchPrefs v32),
  garbage-set rejection, trusted-backstep cap; Join no-reply watchdog + server clock
  skew warning from the LOGIN_OK timestamp prefix (issue #89 follow-up)
- BLE repeats-heard: own-echo RX-log frames pass to BLE one-shot; the #46/#54
  congestion skip stays for everything else (issue #94)
- Mirror app-sent channel messages on-device (was Tanmatsu-only from birth)
- Chat delete: persist the ring purge; mesh-channel drop prefers the pinned slot
- Deep SD history (5000-msg ring) + date separators; chat-list rows restyled compact
- Popup unification: popupClose() + k_popup_registry drive dismiss/count/swipe-block
- Thread/channel sheet as one-screen 2-col grid (contact-sheet layout)
- Keypad nav: strip LVGL SCROLL_ON_FOCUS from collected widgets (About-page jump);
  update dot no longer focusable; verchk waits for an IP + retries with backoff
- Tanmatsu: FIRMWARE_RELEASE_TAG from git describe (drop the hand-bumped hardcode)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
beta_29
2026-07-05 10:07:16 +02:00
Kaj SchittecatandClaude Opus 4.8 5623237951 site: dismissible donate banner -> support the MeshCore founders
Sticky top banner asking would-be donors to help the MeshCore founders instead — links to the blog post (Learn more) and the givealittle donation page (Donate). Dismissible (x + localStorage), and it shifts the corner FAB rails down while shown so they don't collide with the close button.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-04 15:22:37 +02:00
Kaj SchittecatandClaude Fable 5 b90b8bac14 touch: beta_28 — room-server session fix (#89), ST7789 panel sleep, wyvern feedback batch
- Rooms: re-enable the keep-alive pinger (checkConnections), arm a 128s
  keep-alive on LOGIN_OK (servers send 0), blank-relogin self-heal while a
  room chat is open, 'Log in again' in the room sheet, read-only warning.
  Root-caused against simple_room_server: 3 unACKed pushes freeze a client
  until the server hears from it — and we never pinged (issue #89).
- Burn-in: ST7789 SLPIN/SLPOUT on screen-off (backlight-off alone kept the
  panel driving the static image), lit-lock dim guard on all boards, 10s
  notify-wake re-dim, lock clock pixel drift.
- wyvern.red batch: scroll-keys map zoom, backspace exits empty composer,
  Reset path in the chat sheet, compact chat toggle (TouchPrefs v31),
  clearer delivery glyphs + tap-to-resend on failed sends.
- OTA: hide Install-update when the running image's physical address falls
  outside the esp_ota running slot (mixed/legacy tables; beta_21 coredump).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
beta_28
2026-07-04 13:40:02 +02:00
Kaj Schittecat 3585b3a1e0 chore: refresh Mesh America catalog for beta_27 2026-07-04 08:45:41 +02:00
Kaj SchittecatandClaude Fable 5 a4d3715529 release-notes: beta_27 stable rollup (beta_26 + beta_27 since stable beta_25)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-04 08:45:29 +02:00
Kaj SchittecatandClaude Opus 4.8 ffc501d09d touch: hide on-device Install-update button on Launcher installs
touchHasOtaUpdateSlot() checked only that a spare OTA partition exists
(esp_ota_get_next_update_partition != NULL). Under bmorcelli's Launcher a
spare slot exists but is smaller than our image, so the check passed, the
Install-update button showed, and Update.begin() failed "won't fit: Bad Size
Given" (reported by Keith/KM1USR on beta_24 under Launcher). Require the target
slot be at least as large as the running slot; the whole OTA block (Install /
previous-version / beta toggle, already gated on this) then hides on Launcher
installs, where updates go through the Launcher. The update-available notice
still shows.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
beta_27
2026-07-03 08:04:35 +02:00
Yaz 61666ac033 ui-touch: keyboard-backlight brightness control for the T-Deck (issue #84)
The T-Keyboard's C3 firmware takes a 2-byte I2C command [0x01, level 0-255],
but the level the UI drove it at was hardcoded 0xFF, so the backlight was
always 100% when lit. Adds a Keyboard backlight control at the top of
Settings -> Keyboard: a live slider plus Off/25/50/75/Max one-tap presets,
scaling the lit level for the on and auto modes (and the new-message flash).
The percent persists in the existing kbd_bl pref shared with the Tanmatsu
slider; adjusting it while the mode is off flips the mode to on, mirroring
the Tanmatsu control-centre behaviour.

Three latency lessons learned on the way:
- pct->duty is gamma-2 mapped: perceived LED brightness is roughly
  logarithmic in duty, so a linear map felt coarse at the dim end.
- the NVS save is debounced (one-shot timer re-armed per change): the pref
  setter rewrites the whole TouchCfg blob via cfgFlush(), which can block
  the UI thread for a good fraction of a second per tap.
- backlight requests now flush on every touch-poll tick (~8 ms) instead of
  riding the ~32 ms keyboard-scan cadence, and the Settings controls request
  the level directly instead of waiting for the next UITask::loop pass - so
  the control feels as immediate as the screen-brightness slider (whose
  ledcWrite hits the ESP32's own PWM peripheral synchronously).

Signed-off-by: Yaz <yazutsu@users.noreply.github.com>
2026-07-03 07:16:08 +02:00
Kaj SchittecatandClaude Opus 4.8 5f69a61c9c touch: beta_26 — fix SPIFFS-GC watchdog reboot loop (contacts->SD + shared guard)
Decoded a T-Deck coredump (Task WDT, loopTask stuck in spiffs_gc_clean under
DataStore::saveContacts): a contact save on a churned SPIFFS can trigger a
multi-second garbage-collection pass that starves core 0 and trips the task
watchdog, giving a reboot loop.

- Route contacts/channels to SD when a card is present, even on upgraded
  devices whose data is still on SPIFFS (main.cpp mounts SD every boot; if not
  fully adopting SD, store.setSecondaryFS(&SD)). DataStore::begin() migrates the
  existing /contacts3 + /channels2 to the card once; identity/prefs stay put.
- Move the ref-counted WdtHeavyGuard into a shared header so the core
  DataStore::saveContacts shares one count with the UI history/backup saves, and
  wrap saveContacts with it (guaranteed fix for card-less devices).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
beta_26
2026-07-02 21:11:19 +02:00
Kaj SchittecatandClaude Opus 4.8 ddc4a34d4d docs(site): add microSD-card storage tip to the overview
Recommend keeping a card inserted on SD-capable devices (T-Deck, Tanmatsu) so tiles,
contacts and chat history live on the card and avoid the limited/less-reliable internal flash.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-02 11:08:02 +02:00
Kaj SchittecatandClaude Opus 4.8 df4a675cd3 deploy(meshamerica): match official MeshCore device names + add provider description
TJ Downes flagged that provider device names must match MeshCore's canonical names exactly
or they list as NEW devices instead of folding into the existing tiles. Rename to the official
'Heltec v4 + Expansion Kit (Touch)' and 'LilyGo T-Deck' (verified against the live config), and
add the new optional 'description' blurb shown on the provider badge tooltip.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-02 09:40:15 +02:00
Kaj SchittecatandClaude Opus 4.8 8d068cfcc1 docs(site): add 'Bring your own tiles' offline-maps section to the Map page
Explains the microSD tile-pack layout (/maps/osm or /tiles, z/x/y, PNG/JPEG), the
'Tiles from SD card' toggle, making a pack, and per-board differences (T-Deck / V4 / Tanmatsu).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-02 08:58:16 +02:00
Kaj Schittecat c4f31e5a69 chore: refresh Mesh America catalog for beta_25 2026-07-01 23:44:43 +02:00
Kaj SchittecatandClaude Opus 4.8 0af47e5936 release: beta_25 stable notes — aggregate beta_22..25 over stable beta_21
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-01 23:44:40 +02:00
Kaj SchittecatandClaude Opus 4.8 492d0886ec touch: beta_25 (test) — contacts-list refresh, advertise-on-boot, Tanmatsu SD tiles + persistence
- Contacts list rebuilds on advert (force past the count-cache) + a count-delta safety
  net, so a node discovered off-tab or added by the phone app shows immediately (#73)
- New "Advertise on boot" toggle in the Advertise app, off by default (#76)
- Tanmatsu: cache Wi-Fi map tiles on microSD (the P4's internal FFat metadata layer is
  broken — f_getfree/f_stat lie); read tiles via open() not the misreporting exists()/size()
- Tanmatsu: persist contacts/channels/chat on microSD (DataStore secondary FS + chat
  history) so they survive reboot; identity/prefs stay on the (working) internal FFat
- Tanmatsu: boot fix (drop Serial.begin re-init of USB-Serial-JTAG), MQTT disabled, popups
  fit + scroll, keyboard-nav scoped to the open popup, 2000 contacts, Large UI default
- Emoji/quick-reply Esc/red-X closes the popup not the chat; lock-screen unlock after msg-wake

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
beta_25
2026-07-01 23:28:13 +02:00
Dan Vybiral 96dffaef58 touch: fix manual telemetry request reliability + async spinner blink
Two bugs in the manual telemetry request path (the Request button in the
telemetry window):

1. Usually only the second request succeeded. The guest LOGIN and the
   telemetry REQ were sent back-to-back, but a repeater drops a REQ from a
   sender it hasn't added to its ACL yet, and the ACL entry isn't committed
   by the time the first REQ is processed — so the first request got no reply
   and the user had to tap twice. Defer the REQ: uiSendRequestAfterGuestLogin()
   sends only the guest LOGIN and fires the REQ from onContactResponse once the
   LOGIN-OK arrives, by which point we're in the repeater's ACL and a direct
   out_path has been learned, so it lands on the first try. Also adds a
   single-flight guard on telemetryRequestNow(), holds auto-poll while a manual
   request is pending (so it can't clobber the pending reply tag), and disarms
   the deferred login on timeout so a late LOGIN-OK can't fire a stale REQ.

2. The status-bar async spinner only blinked for ~1.5s on a telemetry request
   (the markMeshRequest window) then froze, instead of blinking for the whole
   pending window like a status ping. Its active predicate watched the ping
   deadline but not the telemetry timers; OR in the telemetry pending state.

Auto-poll's best-effort chained send and the admin-console login path are
left unchanged.
2026-07-01 20:52:45 +02:00
Kaj SchittecatandClaude Opus 4.8 3a1495784e touch: beta_24 — signal probe uses standard node-discovery packet
The signal probe now sends a zero-hop NODE_DISCOVER_REQ control packet
(the standard MeshCore node-discovery that the Ultra / KiekR apps use)
instead of a trace, which repeaters do not answer. Repeaters reply
directly with a NODE_DISCOVER_RESP, captured in onControlDataRecv by
matching the probe tag into _ui_sig_* SNR/RSSI. Thanks to Tarmo for
decoding the exact packet.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
beta_24
2026-07-01 14:46:33 +02:00
Kaj SchittecatandClaude Opus 4.8 26c9ccdc07 touch: beta_23 (test) — keep the per-packet RX log off BLE (#46, #54)
The device broadcast a PUSH_CODE_LOG_RX_DATA frame to every connected companion —
including BLE — for every LoRa packet it heard. BLE's ~16 frames/sec budget got
saturated on a busy mesh, delaying/dropping the frames that matter: chat messages
(#46) and repeater/room-server admin responses (#54, the "Streamsink" disconnects).
Skip the RX log on the BLE transport in writeFrameToAll; USB/TCP/WS still get it for
the on-device Monitor app. The MeshCore app never consumed it.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
beta_23
2026-07-01 10:15:13 +02:00
Kaj SchittecatandClaude Opus 4.8 0d8e635848 site: document Stable vs Test channels + how to opt in
Landing page: a plain-language Stable vs Beta explainer under the install channel
toggle, covering both install paths (flash a channel here, or flip the on-device
"Get test builds" toggle). Userguide: a new "Updates & test builds" section (how
updates work, the two channels, how to turn on test builds) plus a Get-test-builds
line on the About settings card.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-01 09:19:23 +02:00
Kaj SchittecatandClaude Opus 4.8 0731b5ff67 touch: beta_22 (test) — trace-ping signal probe + companion contact-refresh (#73)
Signal probe: replace the beta_21 zero-hop advert (which nothing replies to, so the
signal never updated) with a directed TRACE ping to the nearest reachable repeater.
It retransmits — a real reply we measure — without flooding the mesh. New
MyMesh::uiSendSignalProbe() picks the shortest-path repeater; the reply is captured
silently in onTraceRecv via _ui_sig_probe_tag. Falls back to a zero-hop advert when
no repeater is known. Thanks to Tarmo for the insight.

Contacts (#73 part A): a contact discovered while a companion app is connected over
BLE now refreshes the device's own Contacts list + notification. onDiscoveredContact
fired the UI notify only in the standalone branch — move it out so it always fires,
and flag the list dirty for UITask::loop to rebuild on the LVGL thread.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
beta_22
2026-07-01 08:46:56 +02:00
b5e428734c release: credit Samuel Coustet (@samuelcoustet, #71) for the MQTT bridge
The beta_21 MQTT bridge is based on @samuelcoustet's PR #71, reworked for privacy
(consent gate, direct messages off by default, payload encryption). Record the
attribution in the release notes + the version.json "what's new".

Co-authored-by: Samuel Coustet <257299705+samuelcoustet@users.noreply.github.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-30 18:28:31 +02:00
Kaj SchittecatandClaude Opus 4.8 18904c9a5d deploy(nginx): serve the TEST/BETA channel paths on firmware.wadamesh.com
The two-channel rollout added releases/BETA + latest-beta, but the firmware
vhost only had exact-match handling for the old stable paths. Add:
- location = /releases/BETA  -> serves releases/BETA/index.json at the no-slash
  path the device requests when 'Get test builds (beta)' is on (mirrors TOUCH).
- location /latest-beta/      -> the beta web-flasher feed (was hitting the 404
  catch-all).

Stable paths and all bins under /releases/ were already correct; this only wires
up the new beta directory-index + feed.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-30 17:47:46 +02:00
Kaj Schittecat 39a4db8f03 chore: refresh Mesh America catalog for beta_21 beta_21 2026-06-30 17:36:58 +02:00
Kaj SchittecatandClaude Opus 4.8 03e50b0609 touch: beta_21 — first stable build + test channel, MQTT bridge, contacts & signal-probe fixes
New: stable/test update channels with an opt-in "Get test builds (beta)" toggle on
OTA devices (switches both the update check and what you install); experimental
opt-in MQTT bridge (consent-gated, payload encryption, direct messages off by
default); Portugal (Narrow) region preset (#74); saved-contacts counter in the
Contacts overflow menu (#72).

Fixed: the signal probe is now strictly zero-hop so a repeater can't re-flood it;
the contact list sorts before capping so the most relevant contacts show, with a
"+N more — search to narrow" footer (#73); the blocked-users list opened from
Contacts shows the two-line title bar with a back button; the Contacts overflow
popup fits the screen again.

Also lands the two-channel release tooling (scripts/release.sh stable/beta modes +
gen-flasher-meta channel arg), the docs site, and the MQTT reference decryptor.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-30 17:34:11 +02:00
Kaj SchittecatandClaude Opus 4.8 d75db73fc8 release: credit Yazutsu (#66) for the WAV notification sounds in beta_20 notes
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-28 23:50:46 +02:00
Kaj Schittecat 56cf91ef5b chore: refresh Mesh America catalog for beta_20 beta_20 2026-06-28 23:36:03 +02:00