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

7.3 KiB

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)

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

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

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.