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
This commit is contained in:
@@ -0,0 +1,45 @@
|
||||
# Publishing Vela apps to the user's Gitea
|
||||
|
||||
Self-hosted Gitea: https://gitea.oimcloud.myaddr.tools/ — account `codex-bot` (id 55, admin), email codex-bot@bots.com.
|
||||
Auth: `git config --global credential.helper store` + `~/.git-credentials` (chmod 600) holding `https://codex-bot:<token>@gitea.oimcloud.myaddr.tools`. git identity = codex-bot / codex-bot@bots.com. Token is a 40-char hex (Gitea access token).
|
||||
|
||||
## Create a repo (Gitea API)
|
||||
|
||||
```bash
|
||||
TOKEN=<40-char-hex> BASE=https://gitea.oimcloud.myaddr.tools
|
||||
curl -X POST -H "Authorization: token $TOKEN" -H "Content-Type: application/json" \
|
||||
-d '{"name":"vela-snake","description":"...","private":false,"auto_init":false}' \
|
||||
"$BASE/api/v1/user/repos"
|
||||
```
|
||||
|
||||
## Push source
|
||||
|
||||
```bash
|
||||
git init -b main; git add -A; git commit -m "..."
|
||||
git remote add origin "$BASE/codex-bot/<repo>.git"; git push -u origin main
|
||||
```
|
||||
|
||||
## Attach the .rpk as a tagged release asset (two API calls)
|
||||
|
||||
```bash
|
||||
# 1) create release -> returns JSON with an 'id'
|
||||
curl -X POST -H "Authorization: token $TOKEN" -H "Content-Type: application/json" \
|
||||
-d '{"tag_name":"v0.3","target_commitish":"main","name":"SNAKE 0.3","body":"..."}' \
|
||||
"$BASE/api/v1/repos/codex-bot/<repo>/releases"
|
||||
# 2) upload the binary (multipart)
|
||||
curl -X POST -H "Authorization: token $TOKEN" -F "attachment=@dist/foo.rpk" \
|
||||
"$BASE/api/v1/repos/codex-bot/<repo>/releases/<id>/assets?name=foo.rpk"
|
||||
```
|
||||
|
||||
## Open an issue
|
||||
|
||||
```bash
|
||||
curl -X POST -H "Authorization: token $TOKEN" -H "Content-Type: application/json" \
|
||||
-d '{"title":"...","body":"..."}' "$BASE/api/v1/repos/codex-bot/<repo>/issues"
|
||||
```
|
||||
|
||||
## Pitfalls
|
||||
- `node_modules` is a symlink → /tmp/hw/node_modules. `.gitignore` must say `node_modules` (NO trailing slash) — a trailing slash matches directories only, so the symlink was committed until fixed with `git rm --cached node_modules`.
|
||||
- `sign/*.pem` holds the signing key + cert — keep gitignored; NEVER commit private.pem.
|
||||
- `dist/` and `build/` are gitignored; the installable .rpk is delivered via a release asset, not committed to git.
|
||||
- Verify nothing secret leaked: `git ls-files | grep -E 'sign|dist|node_modules|\.pem|\.rpk'` should be empty (the node_modules symlink must also be excluded).
|
||||
@@ -0,0 +1,90 @@
|
||||
# nyan-watch build log (2026-09-07, Watch S4 41mm)
|
||||
|
||||
Minimal resource-light app: one page, ASCII nyan cat on black, no permissions, no images besides icon.
|
||||
Project at `~/nyan-watch/` (user's machine). As of this log the app is NOT yet verified launching on device — this file is the debugging record, not a proven example.
|
||||
|
||||
**Status: v0.8 built (aiot release --enable-jsc --enable-protobuf, own release cert in sign/). STILL black-flashes (~0.02s, nothing visible).** Remaining suspects: (1) release-cert trust on international S4 firmware — diff META-INF/CERT (ours 2.4KB self-signed vs HyperBili's 18KB); (2) designWidth "device-width" vs 466 in config; (3) S4 real-device-debugging OTA requirement (docs say partner-only); (4) watch quick-app partition state. **Control experiment queued: sideload HyperBili's S4 rpk (`~/hyperbili-s4.rpk`, downloaded from hyperbili.astralsight.space) — if IT also fails, the watch/runtime is at fault, not our build.**
|
||||
|
||||
## src/manifest.json (v0.6+ shape — pages/index layout, aiot-toolkit)
|
||||
```json
|
||||
{
|
||||
"package": "com.ilias.nyanwatch",
|
||||
"name": "NYAN",
|
||||
"icon": "/common/icon.png",
|
||||
"versionName": "0.7",
|
||||
"versionCode": 7,
|
||||
"minPlatformVersion": 1000,
|
||||
"features": [{ "name": "system.router" }],
|
||||
"config": { "logLevel": "log", "designWidth": "device-width" },
|
||||
"deviceTypeList": ["watch"],
|
||||
"display": { "backgroundColor": "#000000" },
|
||||
"router": { "entry": "pages/index", "pages": { "pages/index": { "component": "index" } } }
|
||||
}
|
||||
```
|
||||
|
||||
## src/common/icon.png — REQUIRED
|
||||
192x192 png (rainbow stripes + cat box, drawn with PIL). Without this file the watch installer rejects the rpk with a generic "install failed" even though `hap build` succeeds.
|
||||
|
||||
## src/app.ux
|
||||
```js
|
||||
export default { onCreate() {}, onDestroy() {} }
|
||||
```
|
||||
|
||||
## src/Index/index.ux (page dir at src/Index/, NOT src/pages/Index/)
|
||||
Static variant that actually launches on device (animation removed while bisecting a launch crash — the setInterval-with-arrow-closure version installed fine but black-flashed back to the selector):
|
||||
```
|
||||
<template>
|
||||
<div class="page">
|
||||
<text class="nyan">{{ frame }}</text>
|
||||
<text class="hello">{{ title }}</text>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
private: { frame: ",----,\n| |\n| o |\n|____|", title: "NYAN" }
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.page { flex-direction: column; background-color: #000000; align-items: center; justify-content: center; }
|
||||
.nyan { font-size: 16px; color: #ff7f00; font-weight: bold; }
|
||||
.hello { font-size: 24px; color: #ffffff; font-weight: bold; margin-top: 10px; }
|
||||
</style>
|
||||
```
|
||||
|
||||
Animated variant (installs fine; launches unverified on device — suspect):
|
||||
```js
|
||||
onInit() {
|
||||
let i = 0;
|
||||
this._timer = setInterval(() => { i = (i + 1) % FRAMES.length; this.frame = FRAMES[i]; }, 300);
|
||||
},
|
||||
onDestroy() { if (this._timer) clearInterval(this._timer); }
|
||||
```
|
||||
|
||||
## Chronology of this debugging session (read before repeating these steps)
|
||||
|
||||
1. **v1 (hap-toolkit 2.1.1, animated, missing icon):** install rejected — missing `/common/icon.png`. Fix: generate 192x192 png with PIL. (Real fix, verified.)
|
||||
2. **v2 (hap-toolkit, static cat+text, icon present, versionCode 2):** installs, icon shows in app list, but launch = black flash ~0.2s then back to app selector.
|
||||
3. **v3:** removed `setInterval` arrow-closure animation, kept static `private` data → **same crash**.
|
||||
4. **v0.4 (user asked for incrementing 0.1/0.2-style versions on every fix):** stripped page to ONE `<text>`, NO `<script>` at all, added `display.designWidth: 466` → **same crash; this build briefly crashed the whole watch UI** (watch self-restored, no reboot). Since even script-less pages crash, page code is exonerated — the package format/runtime is the suspect.
|
||||
5. **v0.5:** migrated to `aiot-toolkit` 2.0.5 (Xiaomi's real toolchain; bundled official debug certs; jsc bytecode pipeline). Manifest restructured to the Songbird known-good shape: `deviceTypeList:["watch"]`, `features:[system.router]`, `config:{logLevel:"log", designWidth:466}`. **Device verification pending.**
|
||||
6. In parallel, two research subagents swept English (GitHub/Reddit/XDA/mibandnotify) + Chinese sources (米坛社区 bandbbs.cn, luming.cool, CSDN) for anyone with a VERIFIED self-built quick app running on Watch S4/S3 hardware, known 黑屏闪退 causes, and example projects. **Result:** found two verified-real-device projects (see SKILL.md "Verified proven-real-device manifests"): CheongSzesuen/B2048 (AstroBox-published, designWidth "device-width", aiot release --enable-jsc --enable-protobuf) and hrk666666/tgwear-quickapp (minPlatformVersion 1100, aiot build --enable-custom-component). Neither uses hap-toolkit.
|
||||
7. **v0.6 (aiot-toolkit 2.0.5, pages/index layout, static page, designWidth 466):** STILL the same ~0.02s black flash with nothing visible. So layout+toolchain alone were not sufficient.
|
||||
8. **v0.7:** config.designWidth changed to `"device-width"` (B2048 style). SAME crash.
|
||||
9. **v0.8:** `aiot release --enable-jsc --enable-protobuf` with own openssl cert (sign/private.pem + certificate.pem). Package now structurally identical to a proven-working S4 rpk (jsc, .style.bin/.template.bin, manifest-watch.json, same CERT/hash.json scheme — verified by diffing against HyperBili's circle_com.searchstars.hyperbilibili.rpk, cached at ~/hyperbili-s4.rpk). STILL the same ~0.02s black flash. This exonerates the package format entirely; next diagnostic is the HyperBili control install, then watch runtime/partition state or cert trust.
|
||||
|
||||
Also mined from the research: oryonatan repo has known install-error codes from community PRs/issues — error code 3 = storage full, error -6 = pre-installation failed (Redmi Watch 5); INJECT_EVENTS permission needed on Xiaomi/Redmi/POCO phones for the ADB deploy path.
|
||||
|
||||
**Standing hypotheses for the flash (in order to try):** (1) build with `aiot release --enable-jsc --enable-protobuf` (bytecode+protobuf format may be required by the S4 engine); (2) bump `minPlatformVersion` to 1100; (3) debug-signature rejection on international S4 firmware — diff META-INF/CERT against a community rpk that DOES run (download a B2048 AstroBox release rpk and compare); (4) Watch S4 may require the real-device-debugging firmware upgrade for third-party apps (official docs: partner-only OTA).
|
||||
|
||||
## Tooling notes from this session
|
||||
|
||||
- Compiler error `编译失败:请确认manifest.json中配置的文件路径存在:Index/index` means the page dir is in the wrong place — it must be `src/Index/index.ux` (both toolkits).
|
||||
- `resolveFile` in `@hap-toolkit/packager` accepts `.mix`, `.ux`, `.ts` extensions.
|
||||
- Watch for escaped newlines (`\\n` vs `\n`) in multi-line ASCII strings — a doubled backslash renders art on one line.
|
||||
- aiot-toolkit prints output path as `~/.temp_nyan-watch/dist/...` but actually writes to the project's own `dist/` — check `ls dist/`.
|
||||
- aiot build output is plain JS (not jsc) for pages in default `aiot build`; B2048 uses `aiot release --enable-jsc --enable-protobuf` — if a plain `build` rpk black-flashes, try the release flags before suspecting page code.
|
||||
- Symptom guide: install rejected = manifest/icon validation (missing icon file); icon shows but app black-flashes on launch = see chronology above (with hap-toolkit: package format, not page code).
|
||||
- Sideload path: drag `.rpk` into AstroBox (user has it installed).
|
||||
- AstroBox inspects the rpk zip tail for the string "toolkit" (build.txt JSON) to classify it as a quickapp — never strip META-INF/build.txt.
|
||||
@@ -0,0 +1,44 @@
|
||||
# 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.
|
||||
Reference in New Issue
Block a user