89 Commits
Author SHA1 Message Date
Kaj SchittecatandClaude Opus 4.8 bb95aed673 touch: beta_6 — RF Monitor sniffer app + chat icons, popup & scroll fixes
- New Monitor app (app drawer): live RF sniffer — recently-heard feed
  (type / RSSI / SNR / hops, colour-coded), link-margin grade, packet rate,
  and an RSSI + noise-floor scope with an on-screen dBm scale; portrait /
  landscape aware. Backed by a recent-RX ring buffer in MyMesh (logRxRaw).
- Chat: DM person icon / channel group icon; block non-contact senders by
  name; keep scroll position when opening a thread; contacts sort popup
  closes on select; long-press popup close-X no longer overlaps buttons.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
beta_6
2026-06-17 00:37:41 +02:00
Kaj SchittecatandClaude Opus 4.8 e9ece7bf6b touch: tester feedback batch + Launcher/RTC/BLE fixes + timezone picker
Core bump: lib_deps -> meshcomod core-v1.16.3 (BLE advertises the node
name not "NimBLE"; GPS won't push a pre-fix ~1902 date).

Tim's quick-wins: Enter-key-sends toggle, 12-hour clock, lock-when-screen-off,
reverse scrollball.

Launcher / map: fix Wi-Fi tiles never downloading under Launcher (the SD-fallback
cache made tilesFsLowSpace() read the unmounted LittleFS as "full"); plot contact
markers before the tile decode so dots aren't ~2s late.

RTC / time: anchored-time guard re-applies UTC when the clock reads garbage
(GPS clobber); blank pre-2020 timestamps so empty channels don't show 1969/1970;
timezone picker (named zones with correct DST) replacing the CET-only base, with
a v2->v3 config migration mapping prior manual offsets onto a Custom zone.

BLE: persist the random pairing PIN (was re-rolled every boot).

Chat: don't auto-jump to latest on a new message when scrolled up; block-by-name
for non-contact room/channel senders (un-blockable room bots).

Power: drop CPU to 80 MHz when the screen is off; enable Wi-Fi modem-sleep only
once associated (enabling it on the unassociated STA broke the setup-wizard scan).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-16 22:40:55 +02:00
Dan Vybiral 976bf4022c map: fix server + SD tile rendering, add fetch diagnostics (#11)
Community PR by @Vybo (Dan Vybiral). Fixes Launcher SD-fallback tile caching (tilesFsLowSpace read the unmounted LittleFS partition as full and dropped every download), plus write-verification, read/write cap match, SD PNG-pack support, and on-screen fetch diagnostics.
2026-06-16 22:39:35 +02:00
Kaj SchittecatandClaude Opus 4.8 b85e0a4849 site: centre the BY MESHCOMOD byline on the full lockup (mark + word)
Position the byline relative to .brand (the whole mark+word lockup) instead of just the word, so it centres on the combined logo+title (x=640) rather than under the word alone (x=694). The mark stays aligned with the word.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-15 23:58:43 +02:00
Kaj SchittecatandClaude Opus 4.8 e7d6c73d16 site: align mark with the WADAMESH word; byline floats beneath
The mark was vertically centring against the word+byline column, so it sat ~13px below the wordmark. Position the byline absolutely under the word so the mark centres on the word itself. ("Free — as it should be" was already centred on the mark+word lockup.)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-15 23:49:49 +02:00
Kaj SchittecatandClaude Opus 4.8 48ef76fd88 site: Free byline, Telegram + bug buttons, BY MESHCOMOD byline
- BY MESHCOMOD byline under the WADAMESH wordmark.
- Plain "Free — as it should be" line under the open-source badge.
- Two fixed floating buttons: Telegram (top-left -> the meshcomod group) and a bug report (top-right -> the GitHub issues page).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
beta_5
2026-06-15 22:59:34 +02:00
Kaj SchittecatandClaude Opus 4.8 11bd8365d6 beta_5: official-app region scope + contacts/discovered/sound/map overhaul
Companion / core (ported from meshcomod):
- Region/default scope from the official MeshCore app now works: handle
  CMD_SET/GET_DEFAULT_FLOOD_SCOPE 63/64 (companion-v1.16.0.3, issue #31) —
  previously fell through with no reply ("no_event_received").
- US/Canada radio preset corrected to 910.525 MHz / 62.5 kHz / SF7.
- T-Deck +22 dBm TX fix (SX1262 DIO2-as-RF-switch, issue #6) retained.

Touch UI (since beta_4):
- Contacts: person/antenna + red-blocked icons, block/unblock in the popup,
  table with sort/filter + multi-select delete (favourites protected),
  compact time/distance columns, long-name marquee, instant fav-star toggle.
- Discovered: persists across reboots, holds 48, cogwheel settings
  (auto-delete oldest + auto-delete above N hops), "48!" full badge.
- Sound: +/- volume, master toggle, separate Messages/@mention switches.
- Chats: per-row last-message time, per-channel mute, @mention jump-to-message.
- Map: remembers zoom + pan; compact contact popup (no Block button).
- Auto-add on its own page; full-screen settings; refined tab bar; drawer polish.
- T-Deck keyboard backspace at the caret; quick-replies scroll.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-15 22:34:04 +02:00
Kaj SchittecatandClaude Opus 4.8 537148802e fix(t-deck): enable SX1262 DIO2-as-RF-switch — restores full +22 dBm TX (issue #6)
The T-Deck env had SX126X_DIO2_AS_RF_SWITCH=false, so std_init() called
setDio2AsRfSwitch(false). On the T-Deck the SX1262's antenna TX/RX switch is
driven ONLY by DIO2 (no external PA, no TXEN/RXEN pins), so disabling it strands
the +22 dBm PA off the antenna — ~16 dB loss, ~4 mW radiated. That matches the
reported measurement and likely why some sends weren't propagating. Flip to true.

Cross-checked: Meshtastic's T-Deck variant enables it, as does the Heltec V4 here
and every other SX1262 variant in the MeshCore lib — the T-Deck (copied from
upstream MeshCore's identically-buggy lilygo_tdeck variant) was the lone outlier.
LORA_TX_POWER=22, CURRENT_LIMIT=140 and the 1.8 V TCXO were already correct.

Needs an on-air re-measure (no RF meter here); the reporter offered to test.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-15 09:20:31 +02:00
Kaj Schittecat 7b7ea17031 release-notes: beta_4 beta_4 2026-06-14 17:23:19 +02:00
Kaj SchittecatandClaude Opus 4.8 1fe92362be splash: add MESHCOMOD product line; power menu: Download-mode button
- Boot splash now shows MESHCOMOD (small, same size as TOUCH BETA) between the
  WADAMESH wordmark and the TOUCH BETA channel line, with its own fade-in.
- Control-center power menu gains a 'Download mode' button: flushes chat, then
  sets RTC_CNTL_FORCE_DOWNLOAD_BOOT + restarts so the device drops into the USB
  serial bootloader for reflashing — no need to hold BOOT + tap RST. Card resized
  to fit the 4th button on both boards.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-14 17:00:16 +02:00
Kaj SchittecatandClaude Opus 4.8 9b1fd82eb6 map: keep the route-replay HUD clear of the right-edge button column
The HUD pill (hop name + replay button) was full-width top-center, so its replay
button landed under the map's settings gear / zoom column (right edge, x~sw-36).
Anchor it top-LEFT and cap its width to stop before that column.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-14 16:48:23 +02:00
Kaj SchittecatandClaude Opus 4.8 07353263c0 home: cap profile-name width so it can't overlap the clock + slower marquee
The topbar reorder moved the clock left, and the name's short-name path used
LV_SIZE_CONTENT with no width cap — a name <=11 chars but with wide glyphs (W/M)
overran into the clock on the narrow V4. Always cap the label to a fixed window
(100 px T-Deck / 74 px V4); LONG_SCROLL_CIRCULAR stays static when the name fits
and only marquees when it overflows, so no char-count threshold is needed. Also
slow the marquee (anim_speed 14) for readability.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-14 16:44:03 +02:00
Kaj SchittecatandClaude Opus 4.8 beb85bd391 setup: first-boot is Wi-Fi-only — Bluetooth off, opt-in afterwards
ble_en defaults ON, so a fresh device co-inits BLE at boot and the first-boot
setup ran with both radios up. On the fresh-flash path only (setupWizardMaybeOpen,
which returns early once setup is done) force BLE off + persist ble_en=0, before
the UI is interactive — so first-boot setup is Wi-Fi-only and the user opts BLE in
later via the control center. Already-set-up devices return early, so their BLE
state is untouched.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-14 16:26:23 +02:00
Kaj SchittecatandClaude Opus 4.8 2a93779d75 map: stop tile writes before the LittleFS cache fills (fix reboot)
Coredump from a reboot during a route replay: the replay re-centred the map onto
an un-cached area, the fetch task wrote new z16 tiles, and FS::mkdir faulted
inside lfs_alloc — a full/fragmented LittleFS faults rather than returning NOSPC.
The tile cache had no free-space guard or eviction, so heavy map use fills the
4.75 MB partition until the next mkdir reboots the device.

Add tilesFsLowSpace() (cached ~5s, fetch-task-only) and skip tile writes when the
cache has < 320 KB free. Reads keep working so cached areas still display; only
new tiles stop caching once full. The crashing mkdir is never reached.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-14 16:03:07 +02:00
Kaj SchittecatandClaude Opus 4.8 19c856127f map replay: instant popup close + replay button + hop name/id banner
- Tapping 'Replay route on map' now hides the Info popup immediately (was
  lingering over the map transition while the async delete caught up).
- Route-replay HUD: a top banner shows the current hop's repeater name +
  identifier (hash) as each node is revealed, and a replay button (↻) re-runs the
  reveal animation from the first hop. HUD lives on lv_layer_top while the route
  is shown and is torn down when leaving the map.
- RouteNode now carries the resolved name + hex id (routeFillId); 'You' for the
  ME endpoint.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-14 15:51:13 +02:00
Kaj SchittecatandClaude Opus 4.8 8562384f50 perf: stop the 4Hz refresh tick from re-laying-out unchanged labels
Audit of the per-tick UI refresh (runs 4x/s on the UI thread) found ~8 labels
re-written every tick unconditionally — lv_label_set_text always invalidates +
re-lays-out (it never compares), so byte-identical text was redrawn 4x/s on Home
and the control center. Add setLabelIfChanged() (compares against the label's
current text, so it's safe across home/tab rebuilds) and route the status/home/CC
labels through it: CC sysinfo + GPS, home state/IP, RAM/PSRAM, unread, signal
chip, traffic legend, settings footer.

Also coalesce the Wi-Fi tile-arrived re-render: the dirty flag is set per
delivered tile, so a download burst drove back-to-back full-grid tile read+decode
passes on the UI thread — cap to ~2.5x/s (flag stays set; next tick catches up).

Structural levers (DMA flush, double buffer, bigger draw buffer) were left alone:
the audit confirmed they were already tried and reverted for Wi-Fi/BLE RAM, so
they are NOT no-risk. CPU is already 240MHz, tick is esp_timer, touch is 125Hz
async — all already optimal.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-14 15:34:32 +02:00
Kaj SchittecatandClaude Opus 4.8 a10abe1487 fix theme persistence; control-center back-to-dropdown + themed slider/chips
- Theme reverting on reboot: the boot wordmark reads a pref (UI rotation) BEFORE
  SdNvsPrefs::useFile() switches the backend to files, so the settings blob got
  cached from legacy NVS and file-saved values (accent, brightness, language…)
  were ignored every boot. Add touchPrefsReload(), called right after useFile(),
  to re-read from the file backend. (main.cpp now quote-includes TouchPrefsStore.h
  so it sees the new decl, not the monorepo lib's stale copy.)
- Control center: a settings page opened via a chip long-press now returns to the
  dropdown (not Home) when you tap the bar to go Back — tracked with
  s_settings_from_cc.
- Control center now follows the theme accent: brightness slider indicator/knob
  and the toggle chips use COLOR_ACCENT (chips: solid on / faint off) instead of
  a fixed green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-14 15:16:05 +02:00
Kaj SchittecatandClaude Opus 4.8 5f6398b845 home: tappable unread row w/ mail icon; control-center chips long-press to settings
- Home: split the unread count onto its own row with an envelope icon; tapping it
  jumps to the Chats inbox. Memory (RAM/PSRAM) drops to the line below.
- Control center: long-pressing a chip opens that feature's full settings page
  (Wi-Fi, Bluetooth, GPS->Radio & Mesh, Theme->Display, Keyboard, Lock, Sound).
  A short tap still toggles; lv_indev_wait_release swallows the release click so
  a long-press doesn't also flip the radio.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-14 14:55:51 +02:00
Kaj SchittecatandClaude Opus 4.8 f25c9921af msg info: list every route hop, name-only, drop the confusing trailing hash
The route list appended each resolved hop's raw hash hex after its name. With
long repeater names the line wrapped, so a hop's trailing hash glued onto the
next hop's number (hop 2 hash '31' + hop 3 '3.' rendered as '313.'). Now show
just the name when the hash resolves to a known repeater; the hash is a fallback
only for unknown hops. Also drop the 6-hop '+N more' cap and grow the body
buffer so the whole path lists — the popup already scrolls. Same name-only
cleanup applied to the outgoing 'Repeats heard' list.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-14 14:35:26 +02:00
Kaj SchittecatandClaude Opus 4.8 90f8975f45 topbar: close the gap left by the hidden % while charging
When charging the battery shows the bolt only (no % text), but the % label still
reserved its slot, leaving a %-wide gap between the signal bars and the bolt.
Slide the signal/Wi-Fi/Bluetooth/clock cluster right by the reclaimed width on
the charging transition so it stays snug against the bolt; slide back on unplug.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-14 14:16:34 +02:00
Kaj SchittecatandClaude Opus 4.8 fe7a3ed66d topbar: lightning-only when charging + reorder battery/signal/wifi/bt/time
- Charging now shows just the lightning-bolt glyph (batteryGlyphForMv already
  returns it); the 'CHG' text is dropped.
- Right-to-left order is now battery%, signal bars, Wi-Fi, Bluetooth, time.
  Wi-Fi and Bluetooth are split into independent glyphs (were one combined
  label) so each sits in its own slot.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-14 14:09:24 +02:00
Kaj SchittecatandClaude Opus 4.8 d07945c2c1 topbar: clear chat name + cog when leaving the inbox by any route
Starting a route replay inside a channel jumps to the map via goToTab(), which
hides the chat overlay but didn't clear s_chat_title — so the channel name + its
settings cog lingered in the status bar afterwards (e.g. on Home). Clear the chat
title in onTabChanged's leaving-inbox path, so a tab tap, swipe, or replay jump
all drop it (was only cleared by the explicit chat-close button).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-14 13:17:48 +02:00
Kaj SchittecatandClaude Opus 4.8 c8fbc5571f home: drop the list icon from the graph's 'tap for details' cue
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-14 13:08:49 +02:00
Kaj SchittecatandClaude Opus 4.8 79f47dd576 home: move graph tap-cue to top-right + acquire signal fast after boot
- The 'tap for details' cue moves to the graph's top-right corner (was bottom).
- Signal now acquires quickly: the discover probe is a FLOOD advert (repeaters
  re-broadcast it -> we measure the echo's SNR), fired ~4 s after boot and re-
  evaluated every 60 s but only actually sent when we have no recent signal
  (no RX, or older than 100 s). Zero-hop adverts produced nothing to measure
  since they aren't re-broadcast -> the icon sat on 'Sig --'. A chatty mesh that
  already feeds us traffic keeps it quiet; a quiet one refreshes via the echo.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-14 13:03:32 +02:00
Kaj SchittecatandClaude Opus 4.8 7c145df7dd home: in-graph signal chip + a clear 'tap for details' cue
Overlay two chips inside the RX/TX graph box: the live signal ('Sig -3 dB',
top-left) and an accent-coloured 'list  tap for details' cue (bottom-right) so
it's obvious the graph is tappable. Both ride a faint chip to stay readable over
the lines and are non-clickable, so taps fall through to the chart's handler. The
legend above goes back to plain TX/RX traffic counts (signal now lives in the box).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-14 12:57:18 +02:00
Kaj SchittecatandClaude Opus 4.8 75fe3b9e5b home: signal in the RX/TX graph legend + a tap-for-detail popup
The graph legend now leads with the live signal ('Sig -3dB  TX n  RX n  (tap)').
Tapping the graph (or the legend) opens a Signal & traffic popup: SNR, RSSI,
bars 0-4, how long ago we last heard the mesh, the flood/direct TX+RX totals
since boot, and the auto-discover interval. logRxRaw now also records RSSI for it.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-14 12:50:47 +02:00
Kaj SchittecatandClaude Opus 4.8 dee2037310 topbar: mesh signal-strength bars driven by a periodic discover probe
Add a 4-bar signal indicator to the status row, left of the connection icon.
MyMesh::logRxRaw now records the SNR (+ timestamp) of every received packet;
updateGlobalStatusBar maps that to 0-4 lit bars and dims them when nothing's been
heard for 5 min. To keep it fresh when idle, UITask::loop sends a light zero-hop
'discover' advert every 60 s (neighbours only, not flooded -> minimal airtime) so
nearby repeaters/nodes are prompted and their SNR shows up. Home-name cap shrinks
on the narrow V4 to leave room for the bars.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-14 12:40:45 +02:00
Kaj SchittecatandClaude Opus 4.8 6a1770cac4 topbar: show the profile name on the home tab (marquee if long)
The home tab's left status-zone showed the brand 'WADAMESH'; show the user's
profile name instead. Capped to a ~11-char-wide window; longer names
marquee-scroll (LV_LABEL_LONG_SCROLL_CIRCULAR) so they never push into the status
icons. Reconfigured only on a name change (re-setting the scroll mode each tick
would restart the marquee) and reset to content-width when the left zone is
reused by another tab. Empty name falls back to 'WADAMESH'.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-14 12:32:22 +02:00
Kaj SchittecatandClaude Opus 4.8 86c3c122f7 about: stop the per-second sysinfo refresh from stuttering the scroll
The About sheet re-set its ~800-char System-info label every second; re-wrapping
+ repainting that label mid-scroll collided with the scroll redraw and made it
jittery. Skip the refresh while any pointer is actively scrolling/throwing
(lv_indev_get_scroll_dir != NONE) and re-check at 120 ms so live uptime/heap
resumes the moment the user lets go. (General scroll smoothness is a separate
render-path matter — single-buffered, blocking SPI flush.)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-14 12:13:10 +02:00
Kaj SchittecatandClaude Opus 4.8 072253355b prefs: FIX bootloop — keep SdNvsPrefs layout identical to the lib's stale header
My previous commit added an fs::FS* _fs member (and grew _path) to SdNvsPrefs.h.
But the monorepo lib ships a STALE copy of this header that UITask.cpp picks up
via an angle include, so that translation unit saw the OLD layout while the .cpp
saw the new one — an ODR/layout mismatch. The inserted member shifted _sd's
offset, so the stale-header destructor (buildUiTree, on a local SdNvsPrefs) freed
the _sd vector at the wrong offset -> free() of a garbage pointer -> assert ->
panic -> bootloop. Confirmed by coredump (heap_caps_free 'outside heap areas',
~SdNvsPrefs -> ~vector<Kv>, header at .pio/libdeps/.../MeshCore/.../SdNvsPrefs.h).

Fix: keep the DATA LAYOUT byte-identical to the stale header (no added/reordered
members) so mixed headers can't corrupt anything. The file backend now uses
file-static state (s_file_fs/dir/mode) + free helpers instead of an _fs member;
_path stays [40]; only the layout-neutral static useFile() is added (fs::FS
forward-declared, not included). All file-mode behaviour (read-through NVS
migration, file-only writes) is unchanged.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-14 12:05:52 +02:00
Kaj SchittecatandClaude Opus 4.8 3624540002 prefs: store touch settings in files (SD/SPIFFS), off NVS
Make the touch app's own prefs filesystem-backed instead of NVS, so it no longer
contributes to the tiny 20 KB NVS partition whose exhaustion (esp. under
Launcher) triggered boot loops. main.cpp calls SdNvsPrefs::useFile() once after
the storage decision: SD (/meshcomod) when a card is the active data store, else
SPIFFS (/prefs); V4 always SPIFFS (no SD). The radio stack + Launcher still use
NVS (not ours to remove).

Backward compatible / in-place update:
- Read-through migration: in file mode each getter falls back to the old NVS
  value (read-only), so existing settings + Wi-Fi creds still load and move to
  the file on their next save — nothing is lost, no re-entry needed. Gated by a
  one-time 'does NVS have our data' probe so fresh devices don't log-spam.
- New writes only ever touch the file; NVS is never written in file mode.
- On-disk .kv format unchanged, so existing /meshcomod/<ns>.kv files keep working.
- Legacy NVS-or-SD path kept intact for before useFile() runs (early boot read)
  and as a fallback, so the device stays byte-compatible if it's never reached.
- factory-reset (clear) also wipes the legacy NVS copy so it's permanent.

main.cpp includes SdNvsPrefs.h quoted to pick wadamesh's src/ copy over the
monorepo lib's stale one.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-14 11:52:32 +02:00
Kaj SchittecatandClaude Opus 4.8 2b224f4d8c about: show NVS partition usage in System info
Add an 'NVS (settings store)' line to the About sub-tab's live System info via
nvs_get_stats() on the whole 'nvs' partition: used/total entries + percent, free
entries, namespace count. NVS is shared by Wi-Fi creds, the Launcher and our
settings, and filling it is what triggers the boot loop — so surfacing how full
it is makes that diagnosable at a glance (refreshes ~1 Hz like the rest).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-14 11:16:37 +02:00
Kaj SchittecatandClaude Opus 4.8 dd919190e7 map: don't poison the tile dedup ring on a dropped/failed fetch
Two fixes for 'downloading forever, nothing happens':
- queueTileForFetch marked a tile 'seen' BEFORE the queue send, so a tile dropped
  by a full queue (or one whose fetch later failed) was never retried. Split the
  ring into check (tileFetchSeen) + mark (tileFetchMarkSeen) and only mark after a
  successful xQueueSend.
- Clear the dedup ring on map-tab open so a level that failed to fetch once gets
  another chance on re-open (the fetch task still skips on-disk tiles).

(The underlying cause was the tiles.wadamesh.com proxy 404ing .jpg; fixed
server-side. These make the client resilient to any future fetch failure.)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-14 11:01:44 +02:00
Kaj SchittecatandClaude Opus 4.8 5efaefbf7f deploy: add tiles.wadamesh.com PNG->JPEG transcode service
The firmware fetches map tiles as .jpg, but the tiles.wadamesh.com proxy was a
plain PNG passthrough (no .jpg handler) -> every Wi-Fi tile fetch 404'd. Add the
transcode service (tile-transcode.py on 127.0.0.1:5005, fetches OSM PNG and
re-encodes JPEG with Pillow) + its systemd unit, and update the nginx vhost to
proxy /{z}/{x}/{y}.jpg and /elev to it while keeping the legacy .png passthrough.
Same design as the meshcomod tile proxy. HTTP-only by design (device can't TLS).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-14 10:54:45 +02:00
Kaj SchittecatandClaude Opus 4.8 6b56ffb091 map: fix task-watchdog reboot from the zoomed-out tile prefetch
Coredump (loopTask stuck in tileCacheExists via queueZoomPackForCenter ->
renderMapTiles) showed the prefetch doing ~58 synchronous LittleFS stat calls
plus a usedBytes() traversal ON THE UI THREAD every render. Zooming out into an
un-cached area fires renders back-to-back as tiles stream in, so this ran in a
tight loop and starved core-0 IDLE past the 5 s task watchdog.

The prefetch now does zero filesystem work on the UI thread: it just enqueues
the pyramid tiles (queueTileForFetch is in-memory: dedup ring + queue send) and
the background fetch task already skips anything already on disk. Dropped the
usedBytes() free-space guard (a full partition fails the write gracefully).
Throttled to re-enqueue only when the center crosses a new z9 tile (~50 km).
Also yield once per dequeued item in the fetch task so a burst of on-disk skips
(now handled there) can't starve IDLE either.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-14 10:22:05 +02:00
Kaj SchittecatandClaude Opus 4.8 2f95304e7b ui: Info popup — buttons above the route, repeaters tinted by bubble colour
- Move the Replay/Trace action row to just below the title (above the route
  info) instead of pinned at the bottom; the metadata scrolls beneath it.
- Colour each repeater's identifier in the Route / Repeats-heard lists with the
  same signature colour it carries as a chat bubble (nodeSigColorHex reuses
  usernameBubbleColors' FNV-1a hue), via LVGL recolor markers on the body label.
  Nameless hops get a stable tint from their hash. Long-press-copy stays plain —
  copyLabelLongPressCb now strips recolor tokens. Body buffer grown to fit them.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-14 10:08:55 +02:00
Kaj SchittecatandClaude Opus 4.8 73fd690149 ui: make the message Info popup body scrollable
Wrap the metadata body in a vertical scroll container with the title and the
action row pinned, and cap the card to the screen height. A long route (many
named hops) now scrolls inside the card instead of overflowing or pushing the
buttons off-screen — also fixes the pre-existing top/bottom clipping of the
oversized card on the short 320x240 T-Deck. Thin visible scrollbar; long-press
to copy still works (drag scrolls, hold copies).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-14 09:56:24 +02:00
Kaj SchittecatandClaude Opus 4.8 af27ebd0a4 map: replay a message's repeater route on the map (Info popup button)
The message Info popup gains a 'Replay' button that draws the repeaters the
message travelled through onto the map and animates the path one hop at a time.

- Received floods: a chain from the first known repeater through to ME.
- Our own sent floods: a star from ME to each repeater that echoed it back.

Repeater hops are resolved to their advertised GPS via a new read-only
MyMesh::uiHopPos(); only repeaters that have shared a position are plotted, and
the button only appears when >=2 nodes are mappable. Tapping it switches to the
map, fits center+zoom to the route, and reveals the amber path/nodes on a timer.
The overlay rides the pan layer (pans/zooms with the map), is freed by
freeMapMarkers each render, and is cleared on leaving the map tab.
onMapTabActivated skips its self-recenter while a replay fit is pending.

The Info popup's bottom row now holds Replay and/or Trace (side-by-side when
both apply). Pairs naturally with the wider offline tile cache, so a
country-scale route has tiles to draw on.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-14 09:50:48 +02:00
Kaj SchittecatandClaude Opus 4.8 1994f6daba map: prefetch a zoomed-out pyramid over Wi-Fi (province -> country -> borders)
The Wi-Fi tile prefetch cached only two single tiles (z12 + z16), both close
range, so zooming out to see a province/country had nothing cached and dead-ended
offline. Cache a small grid around the center at the zoomed-out levels instead:
3x3 at z7 (~590 km, country + all borders), z8 (~295 km, whole country) and z9
(~145 km, a province), plus the existing z12/z16 detail tiles. Goes through the
same rate-capped background fetch path, runs after the visible tiles (so the
current view still downloads first), self-skips anything already cached, and a
512 KB free-space guard keeps it from thrashing a near-full partition. Bumped the
fetch queue (32->64) and dedup ring (16->48) so a full pyramid pass fits at once.
Now zoom-out is reachable down to country+border scale even offline.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-14 09:25:06 +02:00
Kaj SchittecatandClaude Opus 4.8 47e98f186b fix: don't truncate repeater names in message Info (issue #30)
The name buffer was nm[21]; contact names are up to 32 bytes and emoji cost 4
bytes each, so names like the screenshot's emoji repeater got cut mid-name. Size
the buffer to the full 32-byte name (nm[33]) in both the received-route and
sent-repeater renderers, and widen the body[] stop-margin to match the longer
lines. Label is LV_LABEL_LONG_WRAP so long names wrap rather than clip.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-14 09:11:08 +02:00
Kaj SchittecatandClaude Opus 4.8 9e07a5768a feat: name the repeaters behind 'Repeats heard' on sent messages (issue #30)
Sent messages have no inbound route, so #30's repeater-names had nothing to show
there. Now when we hear an echo of our own flood, capture the re-flooding
repeater (the echo's last path hop) into the echo ring (deduped, bounded to 3),
and list those repeaters by name under 'Repeats heard' in the message Info.
Zero added risk: fixed-size store (no growth), hard buffer guards in the
renderer, read-only contact lookup. Covers #30 in both directions now.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-14 08:57:21 +02:00
Kaj SchittecatandClaude Opus 4.8 1134e49c79 feat: repeater names in the message Info route (issue #30)
The inbound-flood route in the message Info popup showed bare hop hashes
(24E0>6401>...). Resolve each hop to its repeater contact name when known, shown
as a numbered 'N. Name  HASH' list (falls back to the bare hash for unknown
repeaters). Implemented with zero added risk: a read-only, null-safe, bounded
name lookup (MyMesh::uiHopName) + a hard buffer guard and a hop cap in the
renderer, so it can never overflow body[] or grow past the card.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-14 08:34:43 +02:00
Kaj SchittecatandClaude Opus 4.8 3104b6eaf6 site: add 512px PNG app icon (for app catalogs / Launcher)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-14 02:20:56 +02:00
Kaj SchittecatandClaude Opus 4.8 5410290c17 fix(wdt): stop the heavy-flash guard from arming the core-1 watchdog (reboot)
A user on beta_2 hit a task-watchdog reboot (task_wdt: IDLE1, loopTask on CPU1).
Decoding the backtrace showed the network task idle, so the UI thread stalled on
a local op. Root cause: in the Arduino S3 build only core 0's idle task is
WDT-subscribed (CHECK_IDLE_TASK_CPU1 is off). The heavy-flash guard called
disableCore1WDT() (which fails) then enableCore1WDT(), and the latter
*newly subscribes* IDLE1 via esp_task_wdt_add — arming a core-1 watchdog that
didn't exist before. The next multi-second SPIFFS-GC burst on the UI thread
(history save) then starves IDLE1 and reboots. So the guard CAUSED the very
reboot it existed to prevent. Fix: only touch core 0's WDT; leave core 1 at the
Arduino default (unwatched), so a slow write briefly stalls the UI instead of
rebooting. Built + flashed to the T-Deck.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-13 23:34:08 +02:00
Kaj SchittecatandClaude Opus 4.8 058721fdbc release-notes: beta_3
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
beta_3
2026-06-13 23:07:41 +02:00
Kaj SchittecatandClaude Opus 4.8 ff195bbaa2 ui: rebrand remaining product-name strings Meshcomod -> WADAMESH
The first-boot setup welcome ('Welcome to Meshcomod', + its 10 translations), the
About build line ('Meshcomod TOUCH'), and the terminal banner still said Meshcomod.
Flipped to WADAMESH. Left storage/infra references untouched on purpose: the
/meshcomod data paths, the SD 'MESHCOMOD' FAT volume label, the meshcomod.com
tile/flasher proxies, and the 'meshcomod web' community-preset source.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-13 22:57:26 +02:00
Kaj SchittecatandClaude Opus 4.8 77a7420c96 build: bump core to core-v1.16.2 (RTC seed-floor-on-every-boot fix)
Picks up the ESP32Board.h fix that seeds the time floor after any reset (not just
power-on), so the touch firmware no longer shows bogus 1970 timestamps after a
panic/OTA reboot until Wi-Fi/GPS sets the clock.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-13 22:49:44 +02:00
Kaj SchittecatandClaude Opus 4.8 86c07560e5 perf(nvs): pack 17 scalar touch settings into one blob (shrink NVS footprint)
NVS is a tiny ~20KB partition shared with the Launcher/other apps/Wi-Fi; it was
filling up (Launcher 'Failed to store settings in NVS: 4357', boot loops). The
touch namespace spread 17 scalar settings (brightness, kb backlight, accent,
language, rotation, lock colour, ...) across 17 separate keys. Pack them into one
versioned 'cfg' blob -> ~16 NVS entries reclaimed per device.

One-time migration is crash-safe (write blob, then remove legacy keys only after a
durable write; idempotent via magic+ver), preserves every user's existing settings,
and falls back cleanly if the write fails on an already-full NVS. use_sd/setup_ok
stay standalone (main.cpp reads them at boot); strings, byte-blobs and Wi-Fi slots
are untouched. Public API unchanged; both touch envs build.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-13 22:40:29 +02:00
Kaj SchittecatandClaude Opus 4.8 ec3b47bbc2 fix: restore 'repeats heard' on scoped floods + guard trace-route crash
- MyMesh.cpp logRxRaw: the self-echo parser tested (raw[0] & 0x80) for transport
  codes, but 0x80 is the top of the payload VERSION field, not a flag. Transport
  codes are present iff route_type (raw[0]&0x03) is TRANSPORT_FLOOD/DIRECT. Since
  beta_12's region scope, scoped floods carry transport codes -> the parser hashed
  the wrong payload slice -> the echo never matched -> 'repeats heard' showed 0 for
  anyone with a region set. Now route-type aware (matches uiStashRxMeta). Also fixed
  the diagnostic dst= decoder + the header-layout comment.
- MyMesh.h uiSendTraceRoute: walking c.out_path with an unchecked out_path_len
  (uint8_t, can be 65..254 if a contact's path is corrupt) overran sendDirect's
  payload memcpy -> reboot when tapping 'Trace route' on a sent DM. Clamp to
  <= MAX_PATH_SIZE, else fall back to the single-hop trace.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-13 22:11:17 +02:00
Kaj SchittecatandClaude Opus 4.8 9e2eed1551 fix: bound advert-name copy (corrupt contacts) + log NVS headroom at boot
- MyMesh.cpp: strcpy(p->name, contact.name) could overrun the 32-byte advert_paths
  name field when an over-the-air advert carries a 32-char (unterminated) name,
  corrupting the recently-heard/Found table. Bounded with strncpy + NUL-terminate.
- main.cpp: log nvs_free_entries at boot so a filling NVS (the boot-loop trigger
  under Launcher) is visible in serial before it goes catastrophic.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-13 21:45:08 +02:00