Drop myapp.lua into /meshcomod/apps/ on the SD (any reader, the Files
app, USB) and it appears in the drawer named after the file (ver 'dev').
Manifests stay the store's install format; the second scan pass only
picks up ids no manifest claimed. macOS ._* AppleDouble junk skipped.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The built-in Snake tile and embedded copy are gone where the host exists,
install it from the catalog like any app (non-Lua boards keep the native
game). Installed app tiles gain a long-press remove with a confirm sheet;
the post-long-press release click is swallowed so the app does not launch
under it. Store 'Remove' and the tile path share one removal helper.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The Store (new drawer tile) lists the VPS catalog (apps.json on
firmware.wadamesh.com, fetched on the net worker like the version check),
installs/updates/removes Lua apps (two files under <root>/apps/, .sav kept
across reinstalls), shows sideloaded apps, and hosts show/hide switches
for the built-in drawer tiles (app_hide bitmask, prefs v46 appended under
the schema's trailing assert). Installed apps appear as drawer tiles and
launch through the host. Catalog + snake 1.0 staged under out/firmware/apps
and live on the VPS (nginx /apps locations added).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
LuaAppHost: one sandboxed app at a time as an AppPage overlay, every
callback under a 100k-instruction budget + traceback pcall (an app error
is a toast and a clean close, never a reset — proven live when the first
Snake build errored: 4 contained retries, zero leak). Bindings: wada.ui
canvas/label/button + theme colors, hardware-swipe/trackball/touch input,
timer, sys (millis/toast/board/random), per-app KV store under the data
root. Snake rewritten in Lua as the embedded reference app; the drawer
launches it via the host and prefers a user-editable /apps/snake.lua.
CAP_LUA_APPS defaults on for all touch boards. T-Deck: 85.1% flash
(host layer = 13 KB over the VM), app opens at 19 KB PSRAM.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Vendored 5.4.7 core+stdlib under lib/lua (mains excluded), LUA_32BITS
baked into luaconf.h so the VM and embedders share one ABI. LuaHost spike
behind WADA_LUA_SPIKE: PSRAM-only allocator with a hard 256KB cap,
io/os-free sandbox, instruction-budget hook. Measured on the T-Deck:
flash +96KB, state+libs 7.5KB/2ms, hostile while-true contained in 30ms,
zero leak. Inert (byte-identical build) when the flag is off.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
backupInternalFs() still returned FFat on the T-Display P4, but the hot
store moved to LittleFS('storage') at beta_54 and FFat is never mounted
since - every profile export and backup failed at open with 'can't open
file'. The list and restore paths share the helper, so all three heal.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The PR inserted LANG_HU at slot 1 (shifting every stored language choice)
and dropped LANG_RO from the enum while the table still carries all 14
columns, so it did not compile as-is. Romanian is restored to the enum in
the table's column order, a one-time v45 remap keeps every existing user's
language selection, one row had its Hungarian cell pasted twice, and the
enum gained an append-only warning.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Per user feedback on beta_57: the retry default is the user's choice to
make. Applies to the v45 migration line, the fresh-install default, and
the pre-UI mesh state.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The default lock wallpaper had MESHCOMOD baked into the image pixels
(both the rendered RGB565 embed and the SPIFFS-seeded JPEG) - the beta_56
branding fix only changed the animation text drawn over it. Regenerated
both assets with the WADAMESH wordmark and brand-teal padlock; the JPEG's
size change makes the SPIFFS seeder replace the old copy on next boot.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
beta_57 inserted retry_echo MID-struct (between rx_queue and web_mirror)
instead of at the blob tail. The stored cfg is a raw memcpy, so on upgrade
every later u8 shifted one byte: remote_mode inherited remote_landscape's
force-flipped 1 (v37) - every upgraded device booted into remote mode -
map_tile_debug inherited hist_sync_after's 2 (debug overlay on), and
hist_sync_after fell to 0 (blocking chat-flush fallback disabled).
v45: retry_echo moved to the true tail, a one-time repair resets the
shifted run on v44 blobs (originals unrecoverable - beta_57 re-flushed
over them), and auto-retry is opt-in now (default OFF) per user feedback.
Struct gains a loud append-only warning. Layout verified with a host-side
byte-level test of all three upgrade paths.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Beta 57 inserted retry_echo into the middle of the packed TouchCfg blob, shifting the remote-mode and later fields during migration. Define a v45 layout with retry_echo appended at the true tail and recover ambiguous v44 suffix bytes to safe defaults while preserving the stable prefix. Add a host regression test for v43, broken v44, and v45 blobs.
Signed-off-by: Pixel Perfect <me@pixp.cc>
The Format button (and its busy overlay) is compiled for T-Deck, M9 and
V4-R8, and Copy/Paste for every Files-app board, but both deferred workers
in UITask::loop were gated T-Deck-only. Elsewhere the tap set a pending
counter nothing consumed: the notice stayed up forever and the format
latch blocked SD remounting until reboot. Widen the format worker to the
button's gate (via the per-board sdSharedSPI accessor), ungate paste.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
A repeater name with an emoji (multi-byte UTF-8 cut at a byte cap) or a
literal '#' desynced the LVGL recolor runs in the Discover feed, visually
killing every row after it; same class in the message-info repeat list.
Names are now UTF-8-boundary-cleaned and '#'-escaped at both sites, and
copyUtf8ReplacingMissingGlyphs no longer emits a split glyph at its cap.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
'Retry sends until heard' switch under Radio & Mesh gates mikecarper's retry
train (#207): OFF stops new retry trains (an in-flight one finishes its
schedule), applied live and persisted (cfg v44 retry_echo, default ON).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
_wdtHeavyDepth() is a plain int, but WdtHeavyGuard is taken from both the
UI/loop task and the core-1 history worker (histFlushTaskFn ->
uiSegRunArmedJob -> uiSegAppendRecords/uiSegCompactWrite, each of which holds
one). Both run at priority 1 on core 1, so ordinary tick round-robin can
preempt between the load and the store of d++ / --d.
That race predates this PR, but this PR makes it likely rather than
theoretical: the new guard in rebootDevice() is constructed immediately
before uiHistWaitWorkerIdle(), a deliberate wait of up to 9 s on the very
worker that also takes the guard. Interleave the two and either
- the depth strands above zero, so enableCore0WDT() is never called again
and IDLE0 stays unsubscribed from the task watchdog for the session, or
- the depth drops to zero while a guard is still held, re-arming the
watchdog during the flash burst it exists to cover -- which panics
mid-save and loses exactly the chat history rebootDevice() runs to
persist.
Use std::atomic with an acq_rel fetch_add and a compare-exchange decrement.
The CAS preserves the original `d > 0` underflow guard, which still matters:
doFactoryReset() calls wdtHeavyBegin() with no matching end because it
reboots, so the counter can legitimately stay elevated and an unbalanced end
must not drive it negative.
Signed-off-by: Pixel Perfect <me@pixp.cc>
MeshCore's airtime_factor is the Dispatcher's TX pacing multiplier: duty =
1/(1+af), so a 10% duty limit needs af = 9. The presets wrote af = pct/100
(0.10), which configured every preset user's radio to ~91% duty -- the Home
meter's 'always 91%' was truthfully reporting the misconfiguration, on every
region preset including UK/EU.
- applyMeshRadioPresetFields converts duty% -> factor correctly (clamped to
the core's 0..9).
- findMatchingMeshRadioPreset accepts both the correct and the old buggy value
so previously-configured devices still show their preset in the dropdown.
- healPresetAirtimeFactor (run once at UI begin) rewrites the factor for
devices whose params exactly match a preset and whose stored factor matches
the OLD buggy write -- the exact-match requirement is the guard against
touching deliberate user-set values.
NOTE for the web client: Meshcomod-client/shared/radio_presets.ts documents
the same pct/100 convention and needs the matching fix.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The pager's SD support shipped in pieces (mount ladder with XL9555 card-detect,
tile-cache fallback, shared TFT_eSPI SPIClass) but CAP_SD/CAP_FILESYSTEM were
never flipped from the pre-wiring 0, so the Files app gates and the whole
'Store data on SD' machinery stayed compiled out -- an inserted, mounted,
browsable card that nothing would store to. Flipped the caps, extended the
boot storage selection + SPIFFS->SD migration + SdNvsPrefs routing gates to
TLORA_PAGER, and exported tloraPagerSharedSPI() from the variant so main.cpp
can mount the card at boot on the shared bus like the T-Deck.
Both pager envs + T-Deck + V4 + R8 build.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- #165: the animated post-unlock overlay still said MESHCOMOD; now WADAMESH.
- #191: Discover app rows cap the name and use single-letter node types so the
trailing last-heard age always fits the line.
- #157: the Info popup only read the transport scope for FLOOD messages, so a
region-scoped DIRECT message showed no scope; and senders that carry the
region in transport_codes[1] with codes[0]=0 showed Scope 0000. The scope is
now read for any RX and falls back to codes[1] when codes[0] is zero.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
A card whose power-up handshake only completed at 400 kHz kept 400 kHz as its
SESSION clock — SD.begin's frequency is the operating clock, and neither the
boot ladder (main.cpp) nor the runtime ladder (fmSdTryMount) ever raised it
after a slow success. With the SD as the T-Deck's primary store that meant
loading contacts + history at ~25 KB/s: the reported ~3-minute boot, f_getfree
timeouts making an inserted card show as 0 KB/empty in the file manager, and
backups wedging. Launcher installs hit it most (the Launcher's own SD use
leaves the card needing the slow wake path more often).
Standard SD bring-up is init-slow-then-raise: after any sub-4MHz success both
ladders now re-begin at 4 MHz, falling back to the clock that just worked if
the fast re-begin fails. A healthy card is unaffected (mounts at 4 MHz on the
first rung). T-Deck + V4-R8 + M9 (the shared-SPI SD boards) build.
Likely also behind the long-standing Launcher-mode weirdness cluster together
with the torn-prefs fix (#195/#196): slow-clock sessions made every write
window enormous.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Stale CAT_RADIO from before the settings reorg gave GPS its own category —
every other tile already pointed at its own page. All boards.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>