221 Commits
Author SHA1 Message Date
Kaj Schittecat 56cf91ef5b chore: refresh Mesh America catalog for beta_20 beta_20 2026-06-28 23:36:03 +02:00
Kaj SchittecatandClaude Opus 4.8 b4c941f954 touch: beta_20 - spectrum analyzer, custom sounds, saved Wi-Fi, V4 high-gain RX, flood-scope + channel-send fixes
New: Spectrum analyzer app; custom notification sounds (SD WAV per event);
saved Wi-Fi networks with auto-join; unified chat settings screen; per-channel
mute; Heltec V4.3 high-gain receiver (FEM LNA) toggle; Apps button on the V4
home screen; Blocked list reachable from Contacts + chat settings; Portuguese
(BR) UI language.

Fixed: touch-screen flood adverts now region-scoped so region repeaters relay
them (#68); channel sends matched by name so a message can't go out on the
wrong channel's key; opt-in "scope direct messages to region" so room-server
logins work through a region-only path (#64); Wi-Fi scan no longer cancelled
by the reconnect retry (empty SSID list).

Under the hood: flood-scope rework (channel vs direct floods); Spectrum owns
the SX1262 while open and restores mesh RX on exit; wifiScanIsActive() gate;
FEM KCT8103L LNA wired; TouchPrefsStore migrations for the new prefs. Adds the
previously-untracked device_caps.h.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-28 23:33:44 +02:00
Kaj Schittecat 1e00ac3876 chore: refresh Mesh America catalog for beta_19 beta_19 2026-06-27 15:09:40 +02:00
Kaj SchittecatandClaude Opus 4.8 2b45225e0b touch: beta_19 — scroll-popup crash fix, Radio airtime/telemetry, V4 Home fit
- Fix LVGL scroll-momentum use-after-free reboot when a scrollable overlay
  (emoji / quick-reply / add-channel / channel sheets, Wi-Fi scan, pickers,
  app drawer) is freed mid-throw: new closeScrollOverlay() helper
  (lv_indev_wait_release + lv_obj_del_async). Root-caused from field beta_18
  coredumps.
- Radio & Mesh settings: airtime / duty-cycle readout + "Answer telemetry
  requests" toggle, grouped into RADIO / MESH / SIGNAL sections.
- Accent popup dismissed on Enter-to-send (T-Deck physical keyboard).
- Heltec V4: Expansion-Kit Home env line + chart flow below the stats line
  so they fit the 240x320 screen.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-27 15:08:03 +02:00
Kaj SchittecatandClaude Opus 4.8 dad7d295dc touch: airtime/duty-cycle readout + answer-telemetry toggle in Radio settings
Two firmware-only additions surfacing capability already in the stack:

- Airtime / duty-cycle readout: the radio modal exposed airtime_factor only as a raw
  'AF' number. Add a derived line — '≈ N% max duty cycle · ~M ms / 40-byte packet' —
  computing duty% from MeshCore's af=(100/duty)-1 and the per-packet time-on-air from
  the Semtech closed-form (new loraToaMs helper). Lets EU-868 users see compliance at
  a glance with no measurement.
- Answer telemetry requests toggle: MeshCore already gates telemetry responses on
  _prefs.telemetry_mode_base/env (0=DENY/2=ALLOW_ALL) but it wasn't in the touch UI.
  Add a switch (new UITask::setTelemetryAllow) that sets battery+environment modes;
  location telemetry keeps its own separate setting so a simple 'allow' can't leak
  position.

(RX boosted gain was already a switch in Experimental settings; per-packet RSSI/SNR
is already in the Monitor; region presets already bundle SF/BW/CR — so those research
items were already shipped and not re-added.)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-27 10:21:12 +02:00
Kaj SchittecatandClaude Opus 4.8 2d49fb69ec site: add Tanmatsu support; Telegram -> Discord
- List the Tanmatsu (ESP32-P4) in the hero + meta description.
- Add a Tanmatsu box to the standalone installer: no USB flashing — install from the Tanmatsu app store on-device — plus a link to Nicolai Electronics' shop. Boards grid now auto-fits 3 cards.
- Replace the Telegram floating button with Discord (discord.gg/T8X2UaQ7Sm).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-26 20:45:47 +02:00
Kaj Schittecat 94ef98a12f chore: refresh Mesh America catalog for beta_18 beta_18 2026-06-26 19:54:08 +02:00
Kaj Schittecat 2db46ec190 release: beta_18 notes 2026-06-26 19:51:31 +02:00
Kaj SchittecatandClaude Opus 4.8 11775ce5ac tanmatsu(lora): apply runtime radio-config changes to the C6 modem
The Tanmatsu's LoRa modem lives on the C6 coprocessor, shared with the Tanmatsu OS's own
LoRa settings. pushConfig() no-op'd once RX polling started (the remote link can block
under RX load), so a runtime frequency/SF/BW/CR/power change from the wadamesh radio
settings only updated _cfg and took effect on the NEXT REBOOT — the radio stayed on
whatever the OS or boot last set. That is the 'change the wadamesh frequency but receive
nothing on it' report, and why wadamesh's settings never overrode the Tanmatsu OS's.

Now a runtime change marks _cfg dirty and recvRaw() applies it on the RX-poll thread,
between polls: drop to STANDBY (so the link isn't under active-RX load and lora_set_config
can't block), push, re-arm continuous RX. wadamesh's settings now reach the modem live
and win over the OS config. The '0.00001 MHz' the OS page showed is the C6 frequency
corrupted by the two configs fighting; wadamesh consistently owning the config should
clear it — needs on-device confirmation.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-26 19:44:54 +02:00
Kaj SchittecatandClaude Opus 4.8 7d3b4a4336 tanmatsu: the_mesh in PSRAM on the P4 too (build fix for b986f49)
b986f49 moved the_mesh to PSRAM and changed MyMesh.h's extern to 'MyMesh&', but the
Tanmatsu has its OWN main.cpp (tanmatsu/main/main.cpp, separate from src/main.cpp) that
still declared a plain 'MyMesh the_mesh' global — a conflicting declaration that broke
the P4 build (only the S3 envs were built for b986f49). Apply the same placement-new-
into-PSRAM + reference pattern here; the P4's 32 MB PSRAM gets the same DRAM win.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-26 19:44:54 +02:00
Kaj SchittecatandClaude Opus 4.8 f78e3ed493 touch: settings category labels fit on one line (no mid-word split)
The Settings landing tiles centred their label in card_w - 80 px (40 px margins each
side). On the T-Deck's 2-column landscape layout that left only ~64 px, so a single
word ('Bluetooth') split mid-word and 'Radio & Mesh' / 'Quick replies' wrapped to two
lines. Lay the label out to the RIGHT of the left accent icon and centre within that
region (~100 px usable) instead of symmetric centring — every English category name
now fits on one line, unsplit. Wide cards (V4 portrait) are unaffected; long
translations still wrap at word boundaries.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-26 19:24:53 +02:00
Kaj SchittecatandClaude Opus 4.8 b986f49e1a core: move the_mesh (~42 KB) to PSRAM — reclaim internal DRAM
the_mesh was the single biggest static internal-DRAM consumer at 42 KB (dominated
by the MAX_CONTACTS=100 ContactInfo array inside it). Internal DRAM (~320 KB) is the
scarce resource on the ESP32-S3 — Wi-Fi/BLE DMA + heap fight over it, and the V4 was
hitting ~99% on map-tile load (issue #47b). PSRAM (8 MB) is plentiful.

Place the whole the_mesh object in PSRAM via placement-new into a heap_caps_malloc
(MALLOC_CAP_SPIRAM) block and bind a reference, so every the_mesh.foo() call site is
unchanged. The constructor still runs at the SAME static-init point as the old direct
global (PSRAM is already up then), so timing and behaviour are identical; only the
address moves off internal DRAM. Falls back to internal RAM if PSRAM is absent.
ESP32-only (STM32/RP2040 keep the plain global). The contacts array rides along
inside the object — no vendored-core change.

Measured on heltec_v4 (.dram0.bss): 124160 -> 82000 B = ~41 KB internal DRAM freed.
Verified on the T-Deck: clean boot, [BOOT] mesh ok, reason=0, zero panic markers.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-26 19:11:13 +02:00
f6192aa6e5 touch+deploy: opt-in OpenTopoMap style + persist map link-lines (open PRs)
Implements the three open PRs, with OpenStreetMap kept as the shipping default and
OpenTopoMap added as a user toggle (the deploy PRs flipped the default to topo —
this keeps OSM default and makes topo opt-in).

PR #61 (@Yazutsu) — persist the map 'Show link lines' toggle across reboots:
TouchCfg gains map_show_links (cfg v20, default 1); saved in mapOptLinesCb, loaded
in begin().

PR #52/#53 (@samuelcoustet) — OpenTopoMap tile proxy, adapted:
- deploy: tiles.wadamesh.com.conf keeps the default /{z}/{x}/{y}.jpg on OSM and
  adds /opentopo/ (OpenTopoMap) + an explicit /osm/ alias; tile-transcode.py grows
  _fetch_tile_png/_tile_response_from_png helpers feeding /osm + /opentopo while the
  root route stays OSM.
- firmware: new map_style pref (cfg v21, 0=OSM default, 1=OpenTopoMap) + a
  'Topographic map' switch in Map → Options. Topo tiles are namespaced on disk
  (/tiles/topo) and fetched via the proxy's /opentopo route, so the OSM and topo
  caches never collide and toggling is instant for already-cached tiles. Topo
  bypasses the OSM-only microSD packs and always fetches online; the shared
  (z,x,y) fetch-dedup ring is cleared on toggle so the new style re-queues.

Legal: OpenTopoMap map tiles are © OpenTopoMap (CC-BY-SA) over © OpenStreetMap
contributors (ODbL) + SRTM. The on-map © attribution and the Options → Info credits
sheet switch to that text when topo is active; the proxy keeps a contactable
User-Agent + 14-day cache per OpenTopoMap's tile-usage policy.

Topo needs the VPS deploy (restart wadamesh-tile-transcode + reload nginx) before
the /opentopo route serves; until then topo tiles 404 (handled as a permanent miss,
no retry hammer). OSM default is unaffected.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Yazutsu <andrzej@gruziel.pl>
2026-06-26 18:49:41 +02:00
Kaj SchittecatandClaude Opus 4.8 cff58bbaa2 touch: move Discovered sort to a header icon (always visible)
The inline sort button sat after the empty-list early-return, so a Discovered
list with no entries (the common case when auto-add is on) never showed it, and
even when populated it didn't read as a control. Move it to a ⇄ icon button in the
modal header next to the cog — always present, same proven render path as the
cog/purge, opens the same sort sheet. Purge shifts one slot left to make room.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-26 18:25:31 +02:00
Kaj SchittecatandClaude Opus 4.8 b31e37cbbe touch: contacts Sort sheet closes on a choice + the close-X works
Regression from the previous commit: making the Sort sheet stay open (for the
asc/desc flip) meant picking a sort no longer dismissed it, and the close-X was a
no-op because it reused the backdrop callback whose target==current_target guard
(meant to ignore child taps on the backdrop) rejected the tap on the badge.

- ctSortOptCb applies the choice and closes the sheet again; flip a mode's
  direction by re-opening and tapping the active row (the ▲/▼ arrow shows it).
- The option-sheet close-X now uses an unconditional close handler instead of the
  guarded backdrop one, so it always dismisses (also fixes the Filter + Discovered
  sort sheets, which share the shell).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-26 18:16:11 +02:00
Kaj SchittecatandClaude Opus 4.8 76f0754d0d touch: emoji additions, Chats-header button reorder + QR icon, distance sort
Emoji: bake 4 more Noto colour glyphs (soccer, american football, moai,
transgender flag) and add them to the picker. The trans flag is a ZWJ sequence
the single-codepoint imgfont lookup can't render directly, so its combined Noto
image is keyed on the lead codepoint (U+1F3F3) with the trailing symbol mapped to
the zero-width glyph — the whole sequence renders as the one flag. Only the 4 new
glyphs are baked + spliced (scripts/build/add-emoji.py); the existing 252 stay
byte-identical (no Noto-main art drift).

Chats header: group the three actions at the LEFT edge in the order + (add) /
✓ (mark all read) / QR (share), and draw a real baked QR glyph (qr_icon.h, from
scripts/build/gen-qr-icon.py, recoloured to the text colour) instead of the old
LV_SYMBOL_IMAGE 'picture' stand-in.

Contacts sort: every sort option now toggles ascending/descending — re-tapping the
active row in the Sort sheet flips it and shows a ▲/▼ arrow. New 'Nearest first'
(distance) sort orders by distance from our GPS and excludes contacts that have
shared no location.

Discovered list: gains the same sort sheet (Recently heard / Name / Nearest, each
asc/desc) via a sort button at the top of the list; distance likewise excludes
no-location entries.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-26 18:06:24 +02:00
Kaj SchittecatandClaude Opus 4.8 87cb1c3880 touch: retry transiently-failed map tiles instead of pinning them blank (#20)
renderMapTiles already queues every visible tile (edges + corners) on pan,
and queueTileForFetch marks each one 'seen' the instant it is queued. But the
fetch worker only cleared that mark on success — so a tile whose download hit a
TRANSIENT failure stayed flagged forever and was never re-queued. The heap-
safety gate is the common trigger: during a pan the render thread is decoding a
burst of tiles (heavy internal-DRAM use), so the worker's <12 KB-free skip trips
for several of the freshly-queued edge/corner tiles, which then sit blank until
the user forces a full 'reload tiles'. Wi-Fi blips, a full tile cache and proxy
5xx/timeouts had the same one-shot-and-stuck behaviour.

Add tileFetchForget() and drop the 'seen' mark on every transient failure
(heap gate, Wi-Fi down, cache full, negative/5xx/408/429 HTTP, disk write
hiccup) so the next render miss re-queues the tile automatically. A permanent
4xx rejection still stays marked so we don't re-hammer genuinely-absent tiles.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-26 17:28:41 +02:00
Kaj SchittecatandClaude Opus 4.8 f39e072bd7 touch: stop debug prints corrupting the binary companion stream (#25, #54, #23)
The companion link is a BINARY protocol over Serial (USB-CDC). Unconditional debug
prints in operational paths — [ROOM] login send/resp, [TILE] fetch, [JPG] decode —
interleaved text into that stream and corrupted it. The MeshCore app then throws
'Bad state: Streamsink is bound to a stream' (#54) and the PC-app connect (#25) and
member-room login (#23) fail. Route them through a gated WIRE_DBG macro
(COMPANION_WIRE_DEBUG, off in release) like the existing SYNC_DEBUG_PRINTLN.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-26 17:18:07 +02:00
Kaj SchittecatandClaude Opus 4.8 bed04417ed touch(v4): narrow the status-bar name window so a long name clears the clock (#47a)
On the 240px V4 bar the node-name window (74px) left only ~4px before the clock, so a
long name visually ran into the time. Shrink it to 66px for clearance. (Best-effort
without a V4 on the bench; clock position may need a follow-up once verified on-device.)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-26 17:02:38 +02:00
Kaj SchittecatandClaude Opus 4.8 b1adef7364 touch: keyboard-nav toggle no longer snaps the settings list to the top (#45)
beta_15 preserved scroll on close, but turning keyboard-nav ON still jumped to the
top: the focus group was empty (cleared while nav was off) so navMaybeRebuild had no
focus to keep and fell to the first item, scrolling there. Add a one-shot focus hint
that pins focus to the switch you just toggled.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-26 16:49:09 +02:00
Kaj SchittecatandClaude Opus 4.8 36e53d9a1c crash report: only prompt on a crash-boot, not every reset
'The device restarted after a crash' re-appeared after a manual reset / power-cycle:
the prompt gated only on a coredump EXISTING in flash, and the dump persists across
resets until exported. Gate crashReportMaybePrompt() on this boot's reset reason
(panic / task-WDT / int-WDT) instead. The dump stays exportable from About + esptool.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-26 16:37:50 +02:00
Kaj SchittecatandClaude Opus 4.8 7498c502cc touch: widen task-watchdog grace to 20s to stop flash-op WDT reboots (#56)
Decoded coredumps (device + the beta_16 ELFs) are all task-watchdog panics:
CPU0=IDLE0 CPU1=IDLE1, core 0 in spi_flash_op_block_func. A legitimate-but-slow
flash burst (SPIFFS GC, or a bulk DataStore save = thousands of writes) parks both
cores with the cache disabled, starving both IDLE tasks past the ~5s default so the
TWDT panics. The burst can't be chunked under the limit easily and the per-core
WdtHeavyGuard only covers core 0, so re-init the TWDT with a 20s grace (still
catches a genuine multi-second hang) instead. esp_task_wdt_init re-init reconfigures
the running TWDT (old API on this arduino-esp32).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-26 16:30:33 +02:00
Kaj SchittecatandClaude Opus 4.8 bbb7d31975 touch(tdeck): fix kbd backlight stuck-on after flash (#33) + lock-screen burn-in (#55)
#33: tdeckKeyboardSetBacklight only marked the I2C write dirty on a value CHANGE, and
the cached default is 0. A reflash resets the ESP32 but not the keyboard's C3, which
keeps its lit backlight, so the boot 'off' request (0==0) was never sent. Force the
first write so the C3's retained state is always overwritten.

#55: a hard-locked LIT panel never dimmed (clock burning in) because reveals/taps kept
_last_input_ms fresh. Add a burn-in guard: arm _lock_lit_ms only on a real off->lit
transition and dim the locked panel after a bounded window regardless of the screen
timeout or held/ghost touches.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-26 08:39:40 +02:00
Kaj SchittecatandClaude Opus 4.8 83cf0202f7 tanmatsu app-store: pre-flight validate name/description vs store regex
Catch a comma/em-dash/over-length description locally and abort, instead of
discovering it as a failed verify_metadata check on the upstream PR.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-25 17:56:33 +02:00
Kaj SchittecatandClaude Opus 4.8 142ea87064 tanmatsu app-store: description without comma/em-dash (verify_metadata regex)
The launcher store's verify_metadata check restricts description to
^[A-Za-z0-9_\- .!@#$%^&*()/\\]+$ (no commas, no em-dash). Reword so the
template passes on its own instead of needing a manual PR edit each release.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-25 17:53:36 +02:00
Kaj SchittecatandClaude Opus 4.8 1ca2f4e3e6 scripts: mark tanmatsu-appstore-publish.sh executable
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-25 17:32:21 +02:00
Kaj SchittecatandClaude Opus 4.8 59b1b2ab09 touch(tanmatsu): beta_17 — Vol± gestures, message LED, microSD, backups on FFat, status-bar fixes
- Vol+ short = top-bar dropdown toggle; long hold = sound master toggle
- Message-notification LED: flash on new message, soft green glow on unread; toggle in Display settings
- Backups use the internal FFat partition (export/scan/import) + Import button on the Backups page
- microSD browsable in the file manager (P4 SDMMC slot 0; the C6 radio's slot 1 untouched)
- Status-bar icon overlaps fixed (charging-slide + language indicator now SC-scaled with the cluster)
- Vol- screen sleep/wake/lock (screen-lock work)
- Companion link: TCP server start + BLE bond NVS persistence + app-sent channel-message mirror

All Tanmatsu changes gated #if defined(HAS_TANMATSU); T-Deck/V4 bins stay byte-identical.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-25 17:30:55 +02:00
Kaj SchittecatandClaude Opus 4.8 c62107c28a tanmatsu: untrack esp-idf/esp-idf-tools (were self-referential symlinks)
main committed tanmatsu/esp-idf + esp-idf-tools as symlinks pointing at their
own absolute path (mode 120000). A fresh 'git checkout main' materialises those
self-loops over the real local ESP-IDF install — breaking the build ('too many
levels of symbolic links' at source export.sh) and deleting the gitignored IDF.
Untrack + gitignore them like the build setup expects; the project-local
ESP-IDF is installed locally, never committed.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-24 16:45:04 +02:00
Kaj Schittecat eb0f3cf0b0 chore: refresh Mesh America catalog for beta_16 beta_16 2026-06-24 15:45:04 +02:00
Kaj SchittecatandClaude Opus 4.8 36a328a4f7 release: beta_16 notes
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-24 15:42:41 +02:00
Kaj SchittecatandClaude Opus 4.8 13fca6369f touch: OTA previous-version — one button + version popup + confirm
Reworks the earlier-version OTA UI from three inline buttons into a single
'Install a previous version' button that opens a popup of the available older
betas; picking one shows an are-you-sure confirm (reusing showConfirm) before it
closes and hands off to the existing download/verify/reboot flow.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-24 15:21:37 +02:00
Kaj SchittecatandClaude Opus 4.8 abdb878006 touch: OTA — install a previous version (up to 3 back)
Adds a 'Reinstall an earlier version' section to the Wi-Fi OTA UI (native
dual-OTA boards only): up to three buttons for beta_(latest-1..3), each flashing
that exact published bin via the same download/verify/reboot path. The worker URL
is parameterised on a target version; the existing 'Install update' button now
routes through the same otaStartInstall(latest) helper. Buttons auto-hide when
there aren't that many earlier releases.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-24 15:08:18 +02:00
Kaj SchittecatandClaude Opus 4.8 2b4f2bc29e touch: fix status-bar clock overlapping the node name on the V4
The clock's runtime placement used the wide-bar -142 offset on every board,
overriding the V4's intended -126 build slot. On the narrow V4 portrait bar that
pulled the clock left into the width-capped node-name window — the long-standing
'clock overlaps the name' issue. Make the placement bar-width-aware: <300 px bars
(V4) use -126/-94, wide bars (T-Deck/Tanmatsu) keep -142/-110.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-24 14:47:15 +02:00
Kaj SchittecatandClaude Opus 4.8 79b4f10c25 touch: V4 Sensors tab + Home env widget only when sensors present
Refines the Expansion-Kit UI (#29) so it doesn't intrude on a bare V4: the
Sensors tab and the Home environment widget now appear only when an actual
environment sensor (BME280 / GXHTV3) is detected at boot — battery and GPS no
longer count, so a V4 without the Expansion Kit board shows the normal 5-tab
layout and a clean Home (no empty "Batt ----" / "Temp ...." lines). Adds a
"Show Sensors tab" toggle in Display settings to hide it even when sensors are
present (applies on restart). Tab indices resolve at boot so Settings shifts
back to slot 4 when the Sensors tab is absent.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-24 14:34:29 +02:00
42746ef387 touch: Heltec V4 Expansion Kit UI + mesh console (issue #29)
Heltec V4 Expansion Kit local-sensor UI, board-gated behind HAS_EXPANSION_KIT
(V4 touch env only — T-Deck/Tanmatsu unaffected): a Sensors bottom tab
(atmosphere / GPS / battery + history charts), a Home env summary widget, an
Expansion Kit card in Device settings, and a Local Sensors detail page. Reads
on-board MeshCore sensors (BME280, SHT4x, battery, GPS, buzzer) via the standard
sensor/telemetry API — no new hardware drivers.

Mesh console: enable the existing Terminal on the V4 (HAS_TOUCH_UI), renamed
"Mesh console", with wifi/tcp/ble on-off + ota status quick commands. The
SD-backed file-manager pieces stay T-Deck-only (V4/Tanmatsu get a no-op path).

Tab indices are conditional so non-Expansion boards keep 5 tabs (Settings=4) and
the V4 gets 6 (Sensors=4, Settings=5). The PR's GPS-nav/waypoint and weather
pressure-alarm bundles were intentionally left out.

Ported from PR #29 by @samuelcoustet, re-applied against current main.

Co-Authored-By: samuelcoustet <257299705+samuelcoustet@users.noreply.github.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-24 13:57:40 +02:00
300bf89e1a scripts: add companion-protocol serial test harness
Standalone dev tool (pyserial) that drives the companion binary protocol over a
serial port — DEVICE_QUERY/APP_START, battery/storage, stats, telemetry with a
Cayenne-LPP decoder, and meshcomod CLI commands. No firmware coupling; dev-only.

Ported verbatim from PR #29 by @samuelcoustet.

Co-Authored-By: samuelcoustet <257299705+samuelcoustet@users.noreply.github.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-24 12:41:50 +02:00
45e46cd3ab touch: EU keyboard layouts (FR/NL/DE/ES/IT) + auto-select by UI language
Adds French (AZERTY), Dutch, German (QWERTZ), Spanish and Italian layouts — both
the on-screen keyboards and the T-Deck physical-keyboard maps (KeyboardLayoutId
FR..IT). Picking a UI language now auto-enables and selects the matching keyboard
layout, so choosing French gives AZERTY without a separate trip to keyboard
settings. Accented letters still come from the long-press accent popup; no new
fonts required. Extended the settings layout-enable list to match the larger enum
(was a latent out-of-bounds once the layout count grew).

Ported from PR #29 by @samuelcoustet — the layout data applied cleanly; the
UITask wiring was re-applied by hand against current main (the PR was cut from
beta_12 and no longer merges).

Co-Authored-By: samuelcoustet <257299705+samuelcoustet@users.noreply.github.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-24 12:41:50 +02:00
Kaj SchittecatandClaude Opus 4.8 4bb7c08bf0 site: standalone — 'install a previous version' picker (latest 4 betas + notes)
Collapsible version picker in the standalone install guide: a dropdown of the latest 4 betas (from the GitHub releases API — the firmware-host listing has no CORS), rendered release notes per version, and selecting a version re-points both Install buttons at that build (client-built manifest -> the CORS-enabled firmware-host -merged.bin). Defaults to latest; switching back restores the /latest/ manifest.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-24 12:35:20 +02:00
Kaj Schittecat 97b24c4268 Merge PR #48: per-channel scope shows default region as placeholder (tun0) 2026-06-24 12:11:23 +02:00
Kaj Schittecat 981fcba226 Merge PR #49: contact action-sheet landscape overflow fix (tun0) 2026-06-24 12:11:23 +02:00
Kaj Schittecat 50f73f41b0 Merge PR #50: edge-scroll — modals block background scroll (tun0) 2026-06-24 12:11:23 +02:00
Kaj SchittecatandClaude Opus 4.8 c5c0a04484 touch: restore Wi-Fi OTA self-update on native dual-OTA boards
Re-enables the over-the-air update that #74 stubbed out — the block then was
undersized 1.5 MB OTA slots, and both touch tables are now 3.875 MB A/B. Wi-Fi
self-update runs again on natively-flashed boards (every V4 + standalone T-Deck);
Launcher single-slot installs and the Tanmatsu (AppFS) keep the manual path.

- Gated on touchHasOtaUpdateSlot() (a spare A/B slot exists) — already drives the
  update badge, so Launcher/Tanmatsu never reach the install path.
- Self-contained Arduino Update flow in the touch app (the vendored core's OTA
  allow-list is meshcomod-only, so we don't route through board.startHttpOtaFromUrl).
  Fetches the immutable releases/TOUCH/beta_<N>/<board>.bin over plain HTTP from
  firmware.wadamesh.com on the tile worker (off the UI thread); progress polled by
  a UI timer.
- Update.begin(len) does the spare-slot select AND the size check, so a legacy
  too-small slot fails cleanly and falls back to the manual flasher message.
- Verify (MD5) + set-bootable via Update.end(true), then rebootDevice() (saves
  chat history first). A corrupt/short download leaves the running slot intact.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-24 11:56:04 +02:00
Ruben Laban 10ea0d8399 touch: fix contact action sheet overflow on landscape display
The grid_items count missed the conditional "Show on map" button,
causing the card height to be calculated short when the contact has a
GPS fix. Additionally, on landscape displays (sh=240) the backdrop is
only 218px, which repeater cards with many buttons already exceeded.

Fix both: pre-check the GPS condition and include it in grid_items,
and cap the card's displayed height to the available backdrop space
with vertical scroll enabled when the content overflows. The close
badge is FLOATING so it stays anchored regardless of scroll position.
2026-06-24 11:11:56 +02:00
Kaj Schittecat 475245c90e chore: refresh Mesh America catalog for beta_15 beta_15 2026-06-24 11:01:48 +02:00
Kaj SchittecatandClaude Opus 4.8 1f43a8ba37 release: beta_15 notes — amber power-save battery, @-mention picker (#42), overlay scroll-keep (#45), app-send sync (#46), kbd-nav off by default
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-24 10:58:45 +02:00
Kaj SchittecatandClaude Opus 4.8 d491065719 touch: power-save shows as an amber battery, not a moon icon (T-Deck)
Idle power-save (light-sleep) advertised itself with a separate crescent-moon
glyph in the status bar. Replace it with an iPhone-Low-Power-Mode-style amber
tint on the battery icon (iOS systemYellow): clearer, and frees the bar slot.
The moon stays in the tree but hidden, keeping the layout and its tap handler.

A single applyBattColor() helper owns the battery colour so the map-chrome
restyle (black/white over light tiles) and the per-tick refresh never fight —
the amber overlays whatever base colour the context wants when power-save is on.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-24 10:54:06 +02:00
Ruben Laban ab52385f98 touch: edge-scroll — don't fall through to background when dialog is at scroll limit
If lv_layer_top() has a visible widget at the search coordinate (i.e. a
modal or dialog is open), stop the layer search there regardless of whether
a scrollable was found. Prevents the content behind the dialog from being
scrolled when the dialog itself has no more room to scroll.
2026-06-24 10:49:40 +02:00
Kaj SchittecatandClaude Opus 4.8 d11a5bb6b2 touch: keep list scroll position when closing an overlay (issue #45)
Closing a settings item / modal / chat with keyboard-nav on snapped the
underlying list back to the top. navMaybeRebuild rebuilds the focus group when
the overlay closes, but the previously-focused element lived inside the overlay
and is gone, so focus fell to the first list item and the list scrolled up to
show it. Affected every screen because every overlay closes through this path.

Stash the page item that had focus when leaving for an overlay/chat, and restore
it on return — so focus, and the scroll position, land back where they were.
Falls back to the prior behaviour if that item is genuinely gone. Most visible on
beta_14 (keyboard-nav defaulted on); new installs now default it off.

Reported by ZorkaTheDog (#45).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-24 10:31:47 +02:00
Ruben Laban 80af33e7f9 touch: show default region scope as placeholder in per-channel scope field
When a global region scope is configured, the per-channel override input
now shows it as greyed-out placeholder text instead of the generic "#region",
so users can see at a glance what scope an empty field will inherit.
Falls back to "(no default)" when no global scope is set.
2026-06-24 10:29:35 +02:00
Kaj SchittecatandClaude Opus 4.8 c8849916df touch: show companion-app-sent DMs on the device (issue #46, half 1)
A DM sent from the MeshCore companion app went out over the mesh but never
appeared on the T-Deck: the wire protocol deliberately doesn't echo sent
messages back to clients, so the on-device UI (a separate consumer) never saw
app-originated sends. Add an appSentMsgToContact() UI hook (default no-op for
non-touch UIs); the touch UI mirrors the message as a local outgoing bubble in
the recipient's thread. Called from the CMD_SEND_TXT_MSG handler on first
handling only (a client/transport retry was already mirrored).

Still open: the receive direction (T-Deck-received messages reaching the app) —
queueMessage already pushes RESP_CODE_CONTACT_MSG_RECV_V3 to all transports, so
that half needs a frame capture against the real app to pin down.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-24 09:54:38 +02:00