Files
esp32-lua-api/AGENTS.md
T
evan 26f3fdb6e6 feat(runtime)!: sys.startApp replaces routing, history and app identity
The runtime kept a back stack, a launcher fallback, an app id and a title
because a teardown destroys the Lua that would otherwise hold them. Only
the first of those is true: everything about where an app came from can
ride in the arguments, and the arguments are the one value that has to
outlive the VM.

So the runtime now does four things -- close the state, load a path, hand
the next state its arguments, defer the swap to a batch boundary -- and
sys.startApp(path, args) is the whole of navigation. Routing, history,
titles and data directories move to the Lua file a firmware boots, where
they can differ per product without a flag on Runtime.

Arguments cross as JSON, encoded while the sending state still holds the
table, so a function or a cycle raises at the call rather than stranding
a launch. start(args) receives the decoded table, or nil at boot, which
is how the entry file knows to open its own launcher.

Removes launch, replace, back, canGoBack, getAppID, getAppTitle,
setAppTitle and getAppDataPath, along with the home and data fields.
LANDSCAPE.md goes with them: it recorded a divergence from firmwares that
have since migrated.
2026-08-05 17:04:35 -04:00

32 lines
2.6 KiB
Markdown

# ESP32 Lua API Guidelines
This is a clean contract for repositories under the same owner's control. Choose the best shared
API without preserving old names, signatures, or behavior; consumers migrate to the contract.
Every firmware implements all declarations under `lua/api/core/`. Optional hardware contracts
live under `lua/api/features/`, as one file or one directory per feature; `sys.hasFeature(name)`
guarantees the complete matching contract. Every namespace belongs to exactly one feature or to
core, so a feature is a provider pointer rather than a claim to validate: a panel is the `screen`
feature (`screen` and `tree`, including the saved rotation and theme), registered only when the
firmware supplies a `GuiProvider`, and calibration is `touch.setCalibration()`.
The runtime owns what survives a teardown and nothing else: `sys.startApp(path, args)` closes the
`lua_State`, loads a path, and hands the next one its arguments as JSON. Routing, history, titles
and data directories are that Lua file's, because a back stack that cannot outlive the VM is not a
back stack, and everything else about where an app came from can. Encoding happens in the state
that still holds the table, so unencodable arguments raise at the call rather than stranding a
launch.
A global namespace is a provider contract a firmware implements; anything this library provides
itself is a module instead, so `require` and globals divide by who supplies the code. Those live in
`native/src/bindings/lib/` and register into `package.preload`, which puts them ahead of the SD-card
searcher so nothing shadows an implementation. Pure-Lua modules stay in `lua/lib/`, where shadowing
is allowed because a module there is self-contained: it belongs in `lua/lib/` only if replacing it
can break nothing but itself.
Lua-language sources stay under `lua/`; C/C++ and the vendored interpreter stay under `native/`.
Apps are fully trusted; keep permissions and sandboxing out of scope.
Firmware commits dirty display content and owns panel refresh policy. Binding annotations under
`native/src/bindings/` generate matching `lua/api/` files; regenerate instead of editing files
marked generated. Callbacks are fields on the table an app returns, so a `@lua-app` block generates
a class an app composes (`---@class PaintApp : App, TouchHandlers`) rather than global functions. Each `@lua-module`/`@lua-augment` directive sits immediately above the
`luaL_Reg` table it describes, which is how one source declares several namespaces. This repository owns portable `lua/lib/` modules; shared UI owns theme application and persistence. Use `ble` for BLE/GATT
and reserve `bt` for a future Classic Bluetooth contract.