Every builtin widget is written in the public vocabulary, loaded through the public path — which makes the builtin set the best mod documentation that can exist. When you want to build a thing, open the package that already does it.
The recipe map
| You want | Read | The mechanic |
|---|---|---|
| glyph art from paths | weather | a shape icon family composed from shared pieces, picked by bind |
| a month grid | date | one grid repeater over 38 cells, each cell’s look chosen by when on a kind field |
| live clockwork | clock | Lua-generated dial trees, hands via rotate_bind + pivot = "bottom", the pill on a pure ticker |
| facepiles | discord | a keyed list with overlap, speaking rings via style_when { ring, glow }, motion = "halo" on the ringing avatar |
| album art + scrubbing | media | image with fallback, an art-palette viz, a seek bar with {frac}, one card serving both page slots via show |
| hover docks | apps | hover = "lift" icons, a hover = "scope" tile, hover_show/hover_hide label swap, action = "open:{i}" |
| a conic timer | pomodoro | a ring that style_when-recolors by phase, button glyphs swapped by when |
| transient HUDs | system | volume/brightness/connect/battery — transient contexts, snap bars, timeout_ms |
| an editable sheet | notepad | field multiline, on_widget_state, storage |
| an http widget | weather / finance | async fetch → fold → push; on_page_visibility throttling polls |
| launch tiles | launcher | open scope, tile images from package assets |
The builtins ship inside the app’s assets; with the source checkout they’re
readable under assets/mods/<id>/.
Patterns worth stealing
One payload, many faces
The media package pushes one payload; five contexts bind subsets of it. Don’t maintain per-context state — compose views of one truth.
The waiting state is a state
Every builtin with async data declares its empty look in the tree
(when = { field = "ready", ["not"] = true } gating a placeholder). A
configured page slot is never a hole — yours shouldn’t be either.
Generated trees
displays.lua is a program that returns data — loops and helpers above
the return are free:
local function numerals()
local out = {}
for i = 1, 12 do
local a = i / 12 * 2 * math.pi
out[#out + 1] = {
type = "text", value = tostring(i), size = 9, weight = 700,
at = { 32 + 26 * math.sin(a), 32 - 26 * math.cos(a) }, anchor = "center",
}
end
return out
end
return {
dial = { type = "frame", w = 64, h = 64, children = numerals() },
}
The output is still pure data — generation happens once at load.
Sticky choices belong in Lua
The Discord chip shows one face: the last speaker, sticky until a
different participant speaks. That choice is provider/logic-side —
pushed as solo — because the tree can only declare, never decide.
For a complete third-party package built end-to-end, the worldclock walk-through is exactly that.