docs: add agent guidelines

This commit is contained in:
2026-08-02 10:49:05 -04:00
parent 917f65ae2f
commit 8f890625f7
+63
View File
@@ -0,0 +1,63 @@
# slate32 Agent Guidelines
ESP32 firmware (C++/Arduino) hosting Lua apps off an SD card. C++ owns the panel, touch,
network and persistence; everything user-visible is Lua under `sdcard/`.
## Build and Test
```sh
nix develop -c make test # C++ unit tests, Lua tests, and the stub check
nix develop -c pio run # firmware -> .pio/build/esp32-32e/firmware.bin
nix develop -c make stubs # regenerate stubs/slate32.lua after any binding change
```
`make test` fails if `stubs/slate32.lua` drifts from the bindings. Regenerate rather than
hand-editing it.
## Binding Conventions
Accessors are `getName` / `setName` / `isName`. Bare names are actions (`gui.fillRect`,
`wifi.scan`) or pure conversions (`gui.color`, `http.urlencode`). A missing getter is fine;
an accessor without a prefix is not.
Namespaces have one job each: `gui` device primitives and the live frame, `ui` widgets and
palette, `settings` persisted preferences, `sys` process and runtime, plus `wifi`/`http`/
`fs`/`input`/`log`.
**A setter that requires a follow-up call is a bug in the setter.** `settings.setTimezone()`
applies the TZ itself; `settings.setRotation()` applies the frame and re-clips. The one
unavoidable exception is the theme, because C cannot reload the Lua palette — `ui.setTheme()`
is the seam that pairs them, and apps call that, never `settings.setTheme()`.
**Persisted intent is not live state.** `settings.getRotation()` is what the user saved;
`gui.getRotation()` is the frame being drawn. They diverge on purpose while an app rotates
the panel transiently (touch calibration does). Never resolve one from the other.
Settings live in C++ (`src/settings.h`) because the firmware reads rotation and calibration
before any `lua_State` exists, and calibration again on every touch. Lua reaches them through
bindings so there is one writer.
## Status Bar
`sdcard/lib/statusbar.lua` paints the top strip; the firmware only clips apps out of it and
calls `draw(home)` on a timer. **Invalidation is entirely Lua's**: `draw()` compares each
field against what it last painted and keys the whole cache on `gui.getRotation()` and
`ui.themeName`. A new app gets a fresh `lua_State`, so an empty cache already means "repaint
everything".
The one change Lua cannot observe is an app that painted over the bar while fullscreen —
apps toggle through `statusbar.setFullscreen()`, which drops the cache. Do not add
invalidation flags on the C++ side; the facts that drive the bar are only knowable in Lua.
## Emulator
Full instructions in `.pi/skills/test-e32r40t-firmware/SKILL.md`. Two things that cost time:
- The launcher logs `[lua] home ready`, not "launcher ready". It is `/apps/home`, an app like
any other.
- `wait-frame` is an e-ink command and hangs on this board. Use `wait-idle SECONDS` between
captures, and `wait-log` for everything semantic.
Pure Lua logic belongs in `test/*.lua` against `test/fake_device.lua`, which is the single
definition of the binding surface for host tests. Renaming a binding means editing that file.
Reserve the emulator for the panel, touch and SD.