238 Commits
Author SHA1 Message Date
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
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