Conflict in web/CMakeLists.txt: main's WADA_HOST_BOARD=m9 flavour and the
WADA_SIM_PAGER option both kept, one after the other.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
cmake -DWADA_HOST_BOARD=m9 compiles the M9's key path (HAS_M9_KEYBOARD: the
d-pad, the function keys, the Back ladder, the chat message walk) into the
host app on the web board, so M9 navigation runs headless with
--size 320x240 instead of only on a device. The rest stays the web board
(its caps, no SD). platform/WebM9Keyboard.cpp feeds the host keys in as the
controller's bytes.
Scripts: `key NAME [COUNT]` takes a raw byte (`key 0x81` = MSG) and a count
(several keys queued before one render, as on a busy device). The demo
backend takes WADA_DEMO_CHATTER_MS / WADA_DEMO_CHATTER_BURST so messages
arrive at known times.
UITask gates that named the board where they meant its keyboard now name
HAS_M9_KEYBOARD (keyboard backlight level, map pan, nav always on), the nav
globals and CAP_KEYPAD_NAV also take the host flavour, the M9 loop's
lock-screen service sits behind CAP_LOCK_SCREEN and the CardKB drain steps
aside for the M9 keys. Unchanged on every device build.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The host build laid out as the Pager: 480x222, no touch, keyboard and rotary
encoder as the only input, with the UI's own TLORA_PAGER branches compiled in,
so the Pager's screens can be reviewed and scripted without flashing.
- web/sim_pager/: keyboard/encoder bridge, stubs for the codec, I2S, haptics,
IMU, charger and gauge, README and a tour script. Environment switches fake
the hardware states: WADA_SIM_CHARGING, WADA_SIM_CHARGE_HOT,
WADA_SIM_CARE_HOLD, WADA_SIM_SOC (+ _DROP), WADA_SIM_USE_MA,
WADA_SIM_FACEDOWN, WADA_SIM_PICKUP, WADA_SIM_BAG.
- Script commands for the Pager: holdkey (accent picker; holdkey space to
lock; holdkey shift / fn for the modifier shortcuts).
- device_caps.h: a WADA_SIM_PAGER caps block. Off by default; the normal web
and host builds are unchanged.
- Host fixes it needed: INT_MAX include for glibc, SD.begin(ss, SPIClass&) in
the SD shim, panelSleep() on WebDisplay.
- Host SPIFFS lists a directory the way SPIFFS does: every file, with its
folder in the name ("/lock/a.jpg"). The host keeps such files in real
folders, and readdir() handed the folder back as an entry, which the file
manager showed as a 0 B file (/lock, /msgs). Affects the host builds only.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Josh <8422439+MrYoshii@users.noreply.github.com>
wada.sys.random full range no longer divides by zero, wada.ui.input Enter
submits on touch boards with an external keyboard, the OTA download ends
its HTTP connection when no buffer could be allocated, an accepted clock set
lifts the floor (a chip stuck at it no longer keeps the stale time), the host
build refuses removes with '..', and 10 new host tests plus headless scripts.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
# Conflicts:
# scripts/run-host-tests.sh
# src/helpers/esp32/TouchPrefsStore.cpp
# test/clock_floor_fakes/helpers/AutoDiscoverRTCClock.h
web/scripts/contacts-rooms-filter.wada: the Filter sheet lists Rooms under
Repeaters; picking it leaves only Harbor Room (the demo's room) with the
header reading "Room", and the sheet then has Rooms highlighted.
web/scripts/backup-labels.wada, with five backups seeded in the data
dir's SPIFFS root: Settings > Backups lists 2026-10-20, the two 12 June
backups with their times, and the two undated ones as "Default ·
Internal" and "Default · Internal 2".
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
web/platform/board.cpp: WADA_CLOCK_UNSET=1 skips the boot clock set, so
the UI runs as a device before its first sync (timeIsCurrent() false).
web/scripts/contact-purge.sh, three runs on one data dir of the demo
(Femke heard 3 days ago, Old Node 9): Contact Management opens with the
switch off and 30 days; with the clock unset, Old Node favourited, the
switch on and Days = 2, nothing is deleted; with the clock set and
Femke's chat open through the first pass (3 min after boot) Femke and
Old Node both stay; with the Contacts tab open through it Femke is gone
and the favourite Old Node stays.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
web/platform/board.cpp: WADA_PREFS_FILE=1 routes the settings to the
devices' file-backed store (SdNvsPrefs::useFile on SPIFFS "/prefs", as
src/main.cpp does on a board without SD) instead of the host NVS stand-in.
Off by default, so the demo and the tours are unchanged.
web/scripts/prefs-slot.sh: first boot writes prefs/touch.kv; touch.alt is
then made a damaged read-only file; finishing setup saves into that slot;
a reboot must land on Home. With the current SdNvsPrefs.cpp the log shows
"slot b ... recreating it" and the reboot is on Home; built with the
file as it was before #744 the slot never takes the write and the reboot
shows the welcome screen again.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The host's file layer (web/compat/arduino/vfs_api.cpp) passed readdir()'s
"." and ".." entries through openNextFile() and getNextFileName(). No ESP32
file system returns them, so the firmware's directory walkers do not expect
them, and the boot SPIFFS image purge in UITask::begin() queued "/.." as a
folder and walked up out of <data>/spiffs, deleting every *.png and *.jpg it
reached around the data directory (seen: all screenshots in sibling folders
of a scripted run's data dir were gone after the next run). Both entries are
now skipped, as on the device. Checked with a fresh data dir between marker
files: the markers outside survive, a tile image inside spiffs/ is still
purged.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A ".." segment never names anything on a device partition; on the host it
reaches files outside the data dir. With readdir's dot entries passed
through (fixed in af03d890), UITask::begin's boot purge of SPIFFS images
walked "/../../../" and deleted every .png/.jpg above the data dir. This
is the second line of defence for any future walker or path that escapes:
a destructive call on such a path now fails instead.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
web/scripts/apps/inputlen.lua + web/scripts/lua-input.wada run against the
real LuaAppHost.cpp in the web host build: caps().input_len is true,
random(6) stays in 1..6 with every value seen, random(1, 1) is 1,
random(5, 3) is in 3..5, random(1) is 1, random() is a raw word; 250
characters typed into wada.ui.input(..., 300) come back whole on Enter,
100 typed with no max_len come back as 63; the full 32-bit range returns
integers.
The Lua harness mock now mirrors the current host: sys.random with the
new ranges (deterministic xorshift32), caps().input / input_len, and
wada.ui.input with its argument checks, the 63 default, the 1..2000
clamp and one prompt at a time, answered by answer_input(). Scenarios
inputlen_random and inputlen_input run the same app; every store app's
scenarios still pass.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The host's file layer (web/compat/arduino/vfs_api.cpp) passed readdir()'s
"." and ".." entries through openNextFile() and getNextFileName(). No ESP32
file system returns them, so the firmware's directory walkers do not expect
them, and the boot SPIFFS image purge in UITask::begin() queued "/.." as a
folder and walked up out of <data>/spiffs, deleting every *.png and *.jpg it
reached around the data directory (seen: all screenshots in sibling folders
of a scripted run's data dir were gone after the next run). Both entries are
now skipped, as on the device. Checked with a fresh data dir between marker
files: the markers outside survive, a tile image inside spiffs/ is still
purged.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Based on @oumike's draft PR #709, made to compile and checked off-device.
- P4 SD: Launcher reads the card over SPI, and a card in SPI mode ignores the
native-mode CMD0 every SD_MMC mount starts with until it loses power. After
a failed first mount, power the slot off with all six SD lines driven low
for 300 ms, then retry the first rung before the slower ones.
- P4 store: the boot log names the 'storage' partition and its size, or says
it is missing (Launcher's own partition table from the app-only .bin), and
a boot alert says identity, channels and contacts cannot be saved. No
fallback onto another data partition: under Launcher that belongs to
another app.
- Restore: uiImportBackup reports when the identity or node prefs could not
be written (out_saved). The picker and File Transfer's Restore then say so
instead of "Imported N contacts" and a reboot into an empty store. The
BackupImport enum now sits outside the WADA_WEB_FILE_TRANSFER block (the
draft did not build without it), and the web stand-in mirrors out_saved.
- Two new strings in all 15 .lang files (de, es, fr, nl translated).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A name that opens with emoji, such as a flag and an island, drew an empty
avatar. The chat icon taken from the first emoji in a name (#640) took the
first code point with ANY row in the emoji table, and the table maps the
invisible joiners (U+200D, the U+FE0E/FE0F variation selectors) to an empty
2 x 2 image so text never shows them. The island's U+FE0F won, and the icon
was that empty image. A ZWJ sequence such as man + laptop did the same
through its U+200D.
- The emoji pick skips the empty joiner rows; the real emoji after them
still becomes the icon. A picked chat icon that is a joiner, or a text
symbol the font cannot draw, keeps the plain avatar instead of a blank.
- Initials take whole code points and only letters or digits: emoji,
flags, symbols and combining accents are skipped, so the flag name shows
"TR" instead of two flag halves, "ana" with a tilde shows its capital. A
letter the font only has as the '*' stand-in becomes its plain letter, or
the next letter; "?" when the name has none, never blank.
- The logic lives in src/ui-touch/AvatarText.h (pure) with a host test
(test/test_avatar_text.cpp) that first reproduces the blank pick with the
old rule. The section-label uppercase table moved there unchanged.
- web demo backend: WADA_DEMO_CONTACTS="a|b|..." adds contacts for a
scripted headless run.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The 45 s expiry moves into src/helpers/UiRequestSlot.h, shared by
MyMesh::uiRequestTick and the web stand-in, so one constant and one rule
cover both (firmware behaviour unchanged).
test/test_ui_request_slot.cpp: the boundary (free at 45000 ms, not 44999),
millis() wrap, an unanswered ask holding the admin app's STATUS until the
expiry, an answered one clearing at once, a late reply matching nothing,
the deferred login's own 45 s, and a source check that every slot taker in
MyMesh.h and WebMesh.cpp stamps its time.
web/scripts/ui-slot-expiry.wada: the Discord report end to end in the host
build. WADA_DEMO_SILENT_TELEMETRY=1 makes the demo repeater lose its
telemetry reply; the contact sheet's Telemetry (no deadline without SD)
takes the slot, the admin app logs in and Status stays blank until 45 s
after the ask, then the queued STATUS goes out and Status fills. With the
expiry disabled the same run stays blank.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The status, telemetry, neighbour and region requests share one single-flight
slot in MyMesh. The telemetry auto-poll and the Home sensor widget take it
without a deadline of their own, so one unanswered request held it until
reboot. The repeater admin app then queued every status, neighbours and
access-list request behind it and sent nothing: logged in, blank status,
Refresh does nothing (reported on Discord against beta_99).
uiRequestTick() now expires the slot and the deferred guest login 45 s after
they were taken; every sender with its own deadline gives up well before.
The web build's stand-in gets the same expiry.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
web/dist-nothreads is rebuilt by every web/build.sh web and was tracked
by accident (dist/ and dist-debug/ are ignored), so each web build left
a dirty tree of 6.9 MB of wasm. deploy-webclient.sh and serve.py read
the built folder on disk, which stays where it is.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- File writes go straight to the descriptor: musl's fwrite retried a 0-byte
write for ever, which froze the http copy mid Store install / backup import.
- A copy out of sight lets the node go; a link the node drops soon after
opening backs off, and the page says another app took it.
- The UI's own position is saved in the browser and outranks the node's.
- The home-screen app re-measures the screen once iOS has settled.
- ?nt forces the no-threads engine; scripts/node-store-probe.mjs.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Each browser profile and origin keeps its own sync id (browsers that synced
under the shared id keep it), so every copy gets every message it has not
had. iOS: open the keyboard from touchend too, and a Keyboard button when the
UI edits a field without the phone keyboard up.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Like meshcomod: a short notice in the chooser, Continue opens
wifi.app.wadamesh.com with the address, which connects straight away and
says where the visitor is.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Chrome upgrades http://app.wadamesh.com to https because the name answers
https, so the Wi-Fi copy bounced back to the page that may not open ws://.
wifi.wadamesh.com is DNS-only with no 443, so it stays on http, like
connect.meshcomod.com.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The phone branch (portrait/landscape canvas from the window, the phone's
own keyboard, manifest and icons, a one-row bar, mobiletest) next to the
no-threads bundle selection and the Wi-Fi/https chooser messaging. Both
bundles share the one page, so the http copy gets the phone layout too.
Checked after the merge: host build, smoke, webtest (tiles, location,
Store, backups), nodetest (15/15 incl. the location privacy rule),
mobiletest (iPhone 14, Pixel 7, iPad); on the no-threads bundle over
http://<LAN IP>: the browser tour, the mock-node tour, and the mobile tour
(layout + keyboard pass; Bluetooth is not offered there, by design).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Merges main 8954fe06. The drift alarm caught the repeater admin app's new
members: sendStatusPingForUI, sendTelemetryRequestForUI,
uiSendBinaryRequest (-> onBinaryReply), UiReqKind::Binary and
uiLastLoginInfo/UiLoginInfo. Over a node: CMD_SEND_BINARY_REQ and its tagged
PUSH_BINARY_RESPONSE, the ACL and firmware level of a modern LOGIN_OK. The
demo's repeater and room log in (any password = admin, empty = guest) and
answer status, neighbours, the access list, owner info and CLI lines; the
mock node answers neighbours and owner info.
Also from main: nvs_open/nvs_close in the compat (SdNvsPrefs asks first),
and the tiles partition now has to exist before the UI mounts it: the compat
partition table reports "tiles" (the web board's LittleFS volume). Font
guards keep WADA_WEB next to main's new SenseCAP Indicator.
No-threads bundle: a UI-thread wait called from the page (not the main
loop) no longer sleeps (it hung the flush after a Store install).
A wheel turn counts as user activity (no screensaver mid-scroll).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- the canvas is sized from the space below the bar at about one CSS pixel per
canvas pixel, whole device pixels where possible (crisp): iPhone 14 390x616,
Pixel 7 360x692, iPad 540x686; landscape by height and the window's aspect
(664x358, 734x330, 720x506). A computer keeps the framed 800x480. Same
large-screen layouts, which size themselves from the display.
- a turn restarts the UI in the other layout only when the aspect flips and
the width changes (a keyboard is no turn), never while typing
- the phone keyboard: no focus ring or tab hotkey letters for it
(CAP_NATIVE_KEYBOARD in the nav-keys block), keeps up while tapping the UI
mid-edit; IME composition followed live
- the page: slim one-row bar (slimmer on its side), safe areas, link help as
an overlay, home-screen icons (icons/app-icon.svg + render.mjs, maskable)
- tests: web/build.sh mobiletest (iPhone 14, Pixel 7, iPad): fill > 90 %,
UTF-8 + emoji through the hidden field, IME composition, send, turn and
back with the message kept, chooser, manifest. Labels found by text
(wadamesh.findText) so taps work at any size; the UTF-8 storage check
searches bytes. webtest's phone step checks the portrait canvas.
- WEB_BUILD.md "Phones and tablets"; the stray docs/tmp shots removed
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The http copy of the page (it can reach a node's plain ws://) is never
cross-origin isolated, so it has no SharedArrayBuffer and no pthreads.
web/build.sh web-nothreads (also built by web) makes a second bundle with
WADA_NO_THREADS: the FreeRTOS tasks are Emscripten fibers (Asyncify) run
after each UI step, every wait a yield point (freertos_coop.cpp); delay,
yield and esp_timer follow; HTTP from a task is an async fetch it waits
for. The page loads nt/wadamesh.js when it is not isolated (serve.py
serves nt/), and never shows developer hints off localhost.
Chooser: on http, Wi-Fi with a link to the https copy for USB, Bluetooth
and the location; on https, "Open the Wi-Fi version" opens the http copy
with the address (#wifi=...), and a ws:// connect that fails there as mixed
content (or times out) explains it with the same button.
Checked against http://<LAN IP>: the browser tour (persistence, tiles,
Store, backups) and the mock-node tour pass on the no-threads bundle.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Work in progress, handed to the web-mobile branch:
- the canvas size is the board's resolution, picked at boot from the window
(WebDisplay runtime size, host --size WxH); a phone held upright gets a
480-wide portrait canvas on the large-screen portrait layouts; turning
the phone restarts the UI in the other layout (data kept)
- the phone's own keyboard: a hidden textarea opened inside the tap's
gesture, its edits forwarded as UTF-8 text + Backspace/Enter through the
CardKB path (cardKbUiKey passes BLE_KEY_TEXT through); CAP_NATIVE_KEYBOARD
keeps the UI's own keys down; the screen slides above the keyboard
- viewport/safe-area/overscroll, web app manifest, More menu (Reset too),
bar stays on one row, chooser notes for iOS and the http/https copies,
a mixed-content ws:// failure explains the http copy
- web/scripts/mobile-tour.mjs (iPhone 14, Pixel 7)
Known open: the mobile tour's UTF-8 storage check fails (the bubble shows
the text; the stored history does not match the needle; on desktop,
Playwright's insertText of a non-ASCII char sends no keydown and is lost).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Store: the firmware's own catalog, install and language code over
wadaHttpRequest; http://firmware.wadamesh.com/apps/... becomes the
same-origin fw/apps/... (serve.py and the nginx snippet proxy exactly those
paths). The update check, OTA images and reports stay unreachable
(CAP_FW_UPDATE_CHECK 0); the Store badge fetches the catalog on its own.
CAP_STORE_DOWNLOADS keeps the baked apps and languages as the offline first
load and turns the Apps tab and language downloads on. Other servers
(wada.net) are fetched directly: CORS decides, no open proxy. Installs and
app data live in the backend's IndexedDB data dir.
Backups: the stand-in exports and imports the device's backup file (no
private key); CAP_FILE_HANDOFF downloads an export and imports a file picked
on the computer through the UI's own confirm + import. A restart waits for
the node commands an import queued. The bar's More menu backs up and
restores the whole data dir of this browser as one .tar.
webtest: a Store app installed via the proxy survives a reload and runs; a
backup goes out (download) and into a fresh profile (file picker); a
browser backup restores the app and the settings into another one.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Location: the web board has CAP_GPS 1. web/platform/WebLocation.* is its
SensorManager + LocationProvider, fed by navigator.geolocation (page shell,
only while the UI's GPS is on, the prompt deferred to a tap) or WADA_GPS on
the host, so the GPS switch, status, map centring, distances and
wada.sys.gps (with speed/course) work unchanged. The position reaches a
connected node (CMD_SET_ADVERT_LATLON, displaced by the Location privacy
radius) only while the node's advert-location setting is "share"; the demo
moves its cast to the visitor's location.
Map tiles: wadaHttpRequest serves the firmware's default tile server only:
a synchronous emscripten_fetch from the tile_fetch pthread to the
same-origin tiles/ path (serve.py proxies it; nginx location for the site),
plain HTTP natively. WiFi reports connected while the browser is online.
Fixes the static-init order of the host VFS (the UI's own tiles LittleFS
copied an empty impl).
Polish: Lua apps that need the board's own radio carry "radio": "own" in the
catalog (baked under CAP_RADIO_TOOLS, greyed in the Store); mouse wheel and
trackpad scrolling; Settings > Wi-Fi / Bluetooth explain the page bar
(CAP_WIFI_SETUP / CAP_BT_SETUP); no Download mode (CAP_USB_FLASH); no
receiver settings on Settings > GPS (CAP_GPS_RECEIVER).
Tests: webtest checks the map centres on Playwright's Ghent location and
shows tiles; nodetest checks the location privacy rule against the mock node.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A node on a network this computer cannot route to (Kaj's V4 on another VLAN)
never answers a WebSocket at all, so the page sat on "Connecting" with the
demo's placeholder name and nothing loaded. The WebSocket now gives up after
6 s and retries; after two misses the bar says "Can't reach your node" with
what to check for that link (same network, address, cable, Bluetooth) and a
Back to the demo button.
Also: --pre-js files are now LINK_DEPENDS, so editing the transport JS relinks
wadamesh.js (it silently stayed stale before).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Connect opens a chooser with the transports the browser has (Web Serial and
Web Bluetooth on Chrome / Edge, WebSocket to the node's port 8765 everywhere,
with a note where some are missing). The choice is kept and the UI reboots
into it; each backend has its own data dir (/data/demo, /data/node-<key>) so
demo and node data never mix; a reload reconnects; Disconnect returns to the
demo. The bar shows Demo / Connecting / Connected: <node> over <link> /
Reconnecting, and Reconnect / Pair again when the browser needs a click.
Transports retry a lost link and reopen a granted USB port on plug-in. A node
skips the demo's setup wizard. A link lost mid-handshake no longer reports
ready. web/build.sh nodetest: the flow end to end against the mock WS node.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Backend selection moves into the shared boot: a WadaBackendChoice
(web/mesh/BackendSelect.h) that host_main.cpp fills from --serial / --tcp /
--read-only and web_main.cpp from the page (Module.wadaBackend, with its own
data dir), and board.cpp creates the backend from it. The demo position is
only set for the demo. The companion link reports its state through
g_wada_link_hook (web_main.cpp passes it to Module.wadaLink). The browser
transports' JS joins the Emscripten link as --pre-js. WEB_BUILD.md keeps both
phases' sections. build.sh: "$0" relative to the caller's directory no longer
breaks smoke / serve / webtest.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The page hands one press/release edge to the UI per animation frame, but LVGL
reads the pointer every 30 ms. A press that lasted a single 16 ms frame (a
trackpad tap-to-click, or a synthetic click) was often never seen, so taps
landed at random. A press is now held for at least 70 ms before its release is
delivered.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The scale-to-width rule for phones applied to any window under 600 px wide,
so a short landscape window (a docked or split browser, 571x395 here) drew the
screen taller than its stage and over the page header. It is portrait-only now.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The page accepted one pointer at a time and only cleared it on pointerup or
pointercancel. A release that never arrived (let go outside the window, a
focus change, a synthetic click) left activePointer set and every later press
was ignored: the UI looked frozen. Now lostpointercapture also ends the press,
a window blur releases it, and a new mouse press drops a stale one. A forced
release lands where the finger last was, not at 0,0, so LVGL does not read it
as a drag.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
web/build.sh webtest replays web/scripts/tour.wada in headless Chromium,
Firefox and WebKit through the page's own input handlers, then checks reload
persistence, a scroll fps probe, console errors and a phone-sized touch run.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
pthreads for the UI's background tasks, LVGL + the UI loop on the browser main
thread (emscripten_set_main_loop), IDBFS at /data loaded before main() and
persisted after writes settle and on hide/pagehide; Reboot persists and
reloads. Page shell in the site's Night palette: Demo badge, Connect
placeholder, Reset demo data, Fullscreen; canvas scaled with aspect kept,
crisp at whole device pixels; pointer = finger, keyboard = CardKB codes.
serve.py and nginx-snippet.conf send COOP/COEP; without isolation the page
says so. Release -O2 (wasm brotli 1.14 MB), web-debug variant with -g.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- one change counter for the virtual flash (writes, close, remove, rename,
mkdir, format, NVS commit): the page persists IDBFS when it moves
- HTTPClient through a single hook, wadaHttpRequest() (still offline), replies
served from memory, so map tiles over fetch() is one function later
- WADA_DEFAULT_DATA_DIR; settimeofday stub and pthread_exit task end under
Emscripten (no C++ exceptions there)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
board.cpp owns the board globals and src/main.cpp's setup()/loop() order;
host_main.cpp keeps only the SDL window, event pump and scripted tour, so the
browser front end reuses the boot unchanged. WebDisplay tracks the changed
rectangle for the canvas upload.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A reply that arrives after its command timed out is no longer taken as the
answer to the next command; the ack tags the UI keeps in its history come
from the OS CSPRNG, so a new session cannot mark an old bubble delivered.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The Terminal runs every command on the node (CMD_RUN_CLI_COMMAND, MeshCore
1.18), with the old local subset as the fallback; setBLEPin sets the node's
PIN. WEB_BUILD.md documents the companion backend: protocol mapping,
transports and framing, read-only mode, members moved from unavailable to
working, and the known gaps.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
CompanionBackend speaks the companion frame protocol (stock MeshCore 1.17/1.18,
wadamesh extras handled) over a CompanionTransport: USB serial and TCP natively
(--serial / --tcp), Web Serial, Web Bluetooth and WebSocket in the browser (JS
glue in web/mesh/js, framing in C++). The stand-in gains the node-only events
(self info, raw RX, login, CLI, regions, contact removal) and computes the RX
log, advert routes, repeats heard and message route info from the node's raw
RX pushes, as the device does. Demo stays the default.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
WEB_BUILD.md: the architecture (one source, a new board), every
shared-src change, the MyMesh stand-in and its drift alarm with the
table of inert members, the compat layer, known gaps and the phase plan.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
web/build.sh shots runs the phase-1 tour headless on a fresh data dir
(onboarding, Home, Chats, a conversation, send, Contacts, Settings,
Device, Apps, the Snake Lua app) and writes web/docs/*.png; smoke runs
the same tour for an exit status only.
device_caps.h gains CAP_RADIO_TOOLS and CAP_SCREEN_MIRROR (default 1,
so every board keeps its tiles) and lets a board block preset
CAP_WEB_BROWSER; the web board turns all three off.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The touch UI (src/ui-touch, unmodified apart from WADA_WEB board guards)
builds for macOS with CMake and runs in an SDL2 window: a compat layer
for the Arduino/ESP-IDF/FreeRTOS surface, a MyMesh stand-in with a
MeshBackend interface (DemoBackend now), and the web board's SDL
display/touch/keyboard drivers. WEB_BUILD.md follows.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>