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>
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 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>
- 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>
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>
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>
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>
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>