Files
slate32/AGENTS.md
T
2026-08-02 10:49:05 -04:00

3.0 KiB

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

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.