The kit installs over Web Serial with ESP Web Tools — no PlatformIO, no IDE. Rationale and the manifest-shape decision: ADR 013.
A single static page (web/index.html + flasher.js), four steps:
- Choose a composition — cards built from
web/apps.json(Buddy, Glance), each with a real device screenshot. - Flash — ESP Web Tools writes the selected app. Leave its erase
checkbox unchecked (the default): the multi-part manifest writes
bootloader/partitions/boot_app0/firmwareat their offsets and leaves NVS + spiffs alone, so Wi-Fi creds and characters survive a flash (likepio upload). Tick erase for a clean wipe. - Connect & set Wi-Fi — our own Web Serial client sends
{"cmd":"wifi",...}and polls{"cmd":"status"}. - See it live —
{"cmd":"vdp","on":true}streams the real framebuffer into a canvas (the vdp tee).
Chrome or Edge on desktop only (Web Serial). If a board won't connect, put it in download mode: hold BOOT, tap RESET, release BOOT.
python3 tools/export_web_flasher.py # build + assemble web/firmware/<app>/
cd web && python3 -m http.server 8123 # Web Serial works on localhost
# open http://localhost:8123 in Chrome → pick an app → Connect → flashexport_web_flasher.py runs pio run for each app, copies the four flash
parts into web/firmware/<app>/, and writes the ESP Web Tools manifest.json.
Add --skip-build to reuse existing .pio output, --app buddy|glance to
limit, --version vX to stamp the manifest. The C6 offsets it writes to
(0x0 / 0x8000 / 0xe000 / 0x10000) are load-bearing — see hardware.md.
Binaries are never committed (web/firmware/ is gitignored). The dormant
workflows activate on first push:
release.yml— tagvX.Y.Z→ builds all apps → attachesweb-firmware.tar.gzto the Release → deploys Pages with that firmware.pages.yml— redeploys on pushes tomainthat touchweb/, and on manual dispatch, pulling the latest release's firmware (fetch_release_firmware.py). release.yml owns release deploys; aGITHUB_TOKEN-created release can't triggerpages.yml, so they never collide.
One repo setting (outside git) is required because release.yml deploys Pages
from the tag ref: the github-pages environment must permit it —
Settings → Environments → github-pages → Deployment branch policy → add a
v* tag rule (alongside main). Without it the release job is rejected
at the environment gate before any step runs.
Verify after a release. GitHub Pages occasionally records a deploy as
success/active while the previous artifact stays served (seen when a tag
deploy lands close after a main deploy). Confirm the live version actually
flipped — fetch a rarely-cached path so you hit the origin, not an edge cache:
curl -s https://<owner>.github.io/<repo>/firmware/buddy/ota.bin | strings | grep -m1 'v0\.'If it still shows the old version after ~10 min, re-run pages.yml (Actions → Deploy flasher site → Run workflow) — a clean, uncontended deploy republishes the latest release's firmware reliably.
The flasher then lives at https://<owner>.github.io/<repo>/.