# Watch S4 41mm session log (Sept 2026) — verified facts vs dead ends ## Device Xiaomi Watch S4 41mm, round 1.32" 466x466 AMOLED, HyperOS/Vela 4.0, BES2700BP, 32MB RAM. Sideload via AstroBox (drag-and-drop). AstroBox passes an EMPTY package name + versionCode=1 to the watch; the watch parses the rpk manifest itself during install. ## VERIFIED working pipeline (user-confirmed on hardware) 1. Source: aiot-toolkit 2.0.5 project (lockfile pins @rspack/core 1.7.12). 2. Page .ux MUST contain a real `` block. A page with NO script (or bare JS) compiles but its generated initializer calls into undefined VM data → black screen on device. Reproduced in Node: extracted page JS + `$app_require$` stub → `exports.entry({})` throws `Cannot read properties of undefined (reading 'data')`. 3. `aiot release` (plain, no flags) with `sign/private.pem` + `sign/certificate.pem` (openssl self-signed is fine). 4. Install via AstroBox → launches and renders. - Hello Vela v1.1 = first working launch. Calculator v1.2 (mockup, 19 keys, 480px page) = photo-confirmed rendering. v1.3 = round-fitted layout (tapered rows). - Plain JS (no JSC) release works. JSC/protobuf and Rspack version were NOT the issue; earlier speculation that they were required was wrong. ## DEAD ENDS (do not repeat as hypotheses) - hap-toolkit 2.1.1 output: installs but black-flashes at launch. Root cause never isolated; abandoned for aiot-toolkit. Do not claim 'hap-toolkit is incompatible' as established fact — it's an unexplained observation. - None of these changed the black-flash: designWidth number vs "device-width", page path shape (Index/ vs pages/), signing identity, --enable-jsc --enable-protobuf, aiot-toolkit 2.0.4-beta.3 + rspack 1.2.1 pin. The bug was the missing script block all along. - Claims made during the session that were WRONG: "byte-for-byte comparable to working rpk" (not true), "rspack 1.7.12 runtime incompatible with Vela 4.0" (disproven), "JSC required" (disproven). ## Controls and references - Working control rpk: HyperBili circle build for S4 41mm (device id o63): https://hyperbili.astralsight.space/circle_com.searchstars.hyperbilibili.rpk — runs on user's watch. Cached at ~/hyperbili-s4.rpk. - AstroBox source (install flow): github.com/AstralSightStudios/AstroBox-Public — src-tauri/src/miwear/device/thirdpartyapp.rs, resutils.rs (file-type sniff: zip tail containing 'toolkit' = quickapp), error.rs (PrepareStatus codes: Busy/Duplicated/ExceedQuantityLimit/LowStorage/Downgrade/LowBattery). - Community: bandbbs.cn (Chinese hub, login-walled), luming.cool tutorials, oryonatan/xiaomi-band-development (Band 8 Pro sideload via Mi Fitness hidden ThirdAppDebugFragment). - m0tral's MiColor2_* repos are Huawei DevEco/HML apps for Watch Color 2/S1/S2 — different platform, NOT evidence for S4. ## Doc site structure (iot.mi.com/vela/quickapp) SPA. Route table in assets/js/app..js. Content chunks assets/js/..js. English pages under /en/, Chinese under /zh/ (zh sometimes richer). APILevel version notes under /zh/guide/version/apilevel{2,3,4}.html. Key pages: guide/framework/script/lifecycle.html (page: onInit/onReady/onShow/onHide/onDestroy/onBackPress/onRefresh/onConfigurationChanged; app: onCreate/onShow/onHide/onDestroy/onError), components/general/events.html (click/longpress/touch*/swipe only), features/system/event.html (pub/sub events; S4 supported). NO crown/rotary/bezel/side-button API anywhere in docs. ## Calculator app state (~/vela-calculator, package com.example.hellovela) v1.2 (code 3) mockup confirmed; v1.3 (code 4) round-fitted; v1.4 in progress: functional logic via TDD (tests/calculator.cjs — Node vm harness over the .ux script block). Test list includes: arithmetic, decimals, +/-, percent semantics (50% → 0.5; 200+10% → 220; 200x10% → 20), AC, div-by-zero 'Error' + recovery, chained ops, repeated equals, operator replacement, input cap 9 digits, leading-zero suppression. Layout template uses {{ display }} binding and onclick="press('K')" on every key; the test asserts all 19 keys bound uniquely. Then user pivoted: wants crown-rotate-to-select-key + bottom-button-press-to-confirm. No documented API for either; proposed -scroll approximation pending user approval. ## Calculator iterations v1.4–v1.6 (crown experiment + round-list pattern) - v1.4: functional logic + crown-select experiment (keys in scrollable , onscroll → focused index, tap or onBackPress confirms). USER CONFIRMED: crown DOES scroll the quick-app list on Watch S4 (numbers moved off screen = list scrolled). First hardware evidence that crown → list onscroll works for sideloaded quick apps, even though undocumented. - v1.5: fixes from user feedback — items had no explicit width (content overflowed 'off screen'), float precision (0.1+0.2 → '0.30000000000000004'; fixed with Math.round(r*1e9)/1e9), display auto-shrink (computed displaySize 56/46/38px by length). - v1.6: user reported (1) keys cut off at circle edges, (2) couldn't reach bottom of scroll, (3) focus should be the MIDDLE item not the top, (4) #242426 keys unreadable. Root causes + fixes: * Center-focus mapping mathematically can't reach first/last items → add top+bottom spacer list-items of height keysH/2 − itemH/2 (123px); focus = round((scrollY + KEYS_H/2 − ITEM_H/2 − PAD)/ITEM_H); after each press scrollTo({top: PAD + focused*ITEM_H − KEYS_H/2 + ITEM_H/2, behavior:'smooth'}) to re-center. * Items must have explicit width ≤ chord at the viewport's worst y; contrast #3a3a3e on #000 = 11.3:1, focused amber #f3a633 with black text = 10.3:1. * tests/visual.cjs parses the .ux style block and asserts circle containment, viewport-center focus mapping, full reachability, and WCAG contrast — this caught the containment violation (item sticking 140px out of the circle) that the build alone never surfaces. - v1.6 delivered; user then asked for new app ideas; TOTP authenticator proposed as top pick. ## Sundial app (~/vela-sun, com.ilias.sundial v0.1 code 1) — second app, delivered Sun-path dial using NOAA solar math (src/common/solar.js, bundled into page by compiler). TDD: tests/solar.cjs (Paris noon alt ~45°, Sydney Jan, Svalbard polar night/midnight sun, dayProgress 0→1, night pre-dawn 0 vs post-sunset 1 split at solar midnight, formatCountdown), tests/page.cjs (page update() logic with FakeDate class extending Date for fixed-time tests), tests/visual.cjs (circle geometry from .ux styles). Bugs the tests caught: sunrise-bracket scan direction (h=48→1 finds OLDEST crossing; must scan h=1→48 for LATEST), night ambiguity resolved via solar-midnight rule (night returns 1 until midpoint sunset→nextSunrise, then 0), import-strip regex [^;]+; ate half the file because a ';' appeared inside a later comment (use /import\s+\w+\s+from\s+'[^']*';?/), sun orbit radius 200 > sky dial 200 (reduced to 160), center text block chord check should measure text width not block width. Location hardcoded Paris (48.8566, 2.3522) in onInit — geolocation auto-detect is the v0.2 candidate. User asked for 'cool apps' menu; TOTP authenticator (system.crypto + system.storage) pitched as the killer app.