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