Files
codex-bot 5ba2f24a6e Vela quick-app dev skill + knowledge base (S4 41mm)
- SKILL.md: toolchain, project structure, round-screen layout, TDD harness,
  input/crown findings, per-app notes
- references: gitea-publish workflow, S4 session log, historical nyan-watch notes
- README: overview + key facts
2026-09-09 15:16:30 +01:00

45 lines
7.1 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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 `<script>export default {...}</script>` 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.<hash>.js. Content chunks assets/js/<n>.<hash>.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 <list>-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 <list>, 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.