docs: add agent guidelines
This commit is contained in:
@@ -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.
|
||||
Reference in New Issue
Block a user