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