Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Documenting

How to write and check a page in this book. The book source is docs/, built by mdBook. The code is the only source of truth: verify every behavioural claim in renderer/src, supervisor/src or shared/src before writing it. DECISIONS.md records why things were built; it is history and may be stale, so cite an ADR only as a “why” pointer after the code confirms it.

Commands

CommandDoes
just docsServes the book at http://localhost:3000, rebuilt on save
just bookBuilds the book as CI publishes it, then checks every link and anchor
just stubsRegenerates every lua-meta/*.lua, every docs/capabilities/<name>.md and the generated property tables
cargo test -p renderer doc_examplesRuns every Lua block in docs/ and checks every screenshot (part of just check)
just shotsRe-renders the screenshots that changed, deletes orphans, lists what moved
just rustdocRustdoc for the crates, warnings as errors. Not this book

Lua examples are tests

Every fenced block whose info string is lua or starts lua, runs in cargo test. The test is every_lua_block_in_the_docs_evaluates_and_lays_out in renderer/src/check.rs. Each block goes through the same evaluation and both layout passes as mantle check: no Wayland, no subprocesses, every capability nil and then one sample push each, one 704x396 output named DP-1. The test fails on a Lua error, a check error or a layout error, and names the block by file:line. Blocks inside > quotes count too.

Info stringThe block
luaRuns and lays out. It returns surfaces as shell.lua does, or returns one node, which the test mounts in a panel with 24 px of padding
lua,shotA lua block the book shows a screenshot of (screenshots)
lua,fragmentOnly parses. For a snippet that needs context the page has not given, like a require of another file
lua,must-failMust fail to evaluate or lay out. For showing a mistake
lua,no-checkSkipped. Put the reason in an HTML comment on the line above

Prefer lua over fragment, and fragment over no-check. mdBook turns the comma into a second CSS class, so every tag still highlights as Lua.

A widget example needs no surface around it:

button {
    padding = 6,
    on_click = function() print("clicked") end,
    children = { text { content = "Click" } },
}

Evaluation accepts this width, but layout refuses it:

rect { width = "Wide", height = 10 }

A module file that only makes sense beside another one:

local clock = require("widgets.clock")
return panel { id = "bar", layer = "Top", child = clock }
return { require("bar") }  -- fails
local bar = require("bar")
return { bar }             -- works

Screenshots

A lua,shot block also renders, headless over EGL, and must match its committed image, docs/images/<section>/<page>-<n>.png, the page’s n-th shot. tools/book_links.py puts the image under the block on the site, so a page never links it. Tag the example a reader wants to see; a block has to return a node or surfaces.

PartRule
ImageEvery visible surface stacked top to bottom 8 px apart, each popup where its anchor_rect, anchor, gravity, offset and SlideX put it on its parent, at scale 1 over Catppuccin Mocha crust #11111b. Cropped to the painted pixels plus 16 px; a shot that paints nothing fails
StillDrawn with every tween finished
<!-- shot: frames=0..400/20 --> on the line aboveAn animated PNG: one frame per time, in ms after the last tween started. frames=0,50,120 lists them
docs/images/<section>/<page>.fakes.luaRuns before each shot on the page. fakes = { battery = {...} } is pushed as each capability’s first push, on_change included. __pointer = { surface = "bar", x = 40, y = 12 } rests the pointer there after the first layout, in that surface’s logical px (surface defaults to the first), so hover(), its rect and on_hover answer as for a real pointer. A __after function runs after the first layout, then the shot lays out again: that is how an OSD shows or a card leaves. A popup a click opens needs its anchor state set to the rect that click would pass
<!-- file: shaders/glow.frag --> on the line above any fenced blockThat block is written to that path in each shot’s config directory, mantle.config_dir, so a page’s shots load the file the page shows
PinnedFonts, icons and images come from renderer/fixtures/shots, os.time() is 2026-09-24 12:45 UTC and os.date reads UTC, $USER is user

A quoted absolute path whose file name is in renderer/fixtures/shots/images points at that file. An icon missing from fixtures/shots/icons fails the test with its name; copy it in from Adwaita and note it in fixtures/shots/NOTICE.

FailureFix
<name>.png differs by up to N per channelThe render moved. Compare <name>.new.png beside it. Intended: just shots. Not: fix the regression
is missing or a different sizeA new shot, or its size changed: just shots, then look at the image
no lua,shot block draws this imageA shot was removed or renumbered: just shots deletes it
An APNG passes on one GPU and differs by 100+ on anotherA moving edge lands on exactly half a pixel in some frame, and drivers round that differently. Pick frame times that miss it, such as 0..210/30 over 0..200/20

A render within 2 per channel of the committed image passes and is not rewritten, so another GPU driver never shows up in git. EGL is required: without it the test fails.

Capability pages

docs/capabilities/<name>.md is generated by supervisor/src/stubs.rs from the Rust payload types. Never edit it: write prose in docs/capabilities/intro/<name>.md and run just stubs.

Part of the intro fileLands on the page
Above <!-- reference -->After the one-line blurb, before the generated State and Actions tables
Below <!-- reference -->After the tables: How do I…, Gotchas, See also
No markerAll of it above the tables

cargo test fails while a generated page differs from what just stubs would write. intro/ stays out of SUMMARY.md.

Property tables

The property table on each docs/nodes/<kind>.md and docs/surfaces/<role>.md, the common one on nodes/index.md and the box one on guide/paint.md are generated by renderer/src/lua/nodes/stubs.rs from the typed fields in renderer/src/lua/nodes/properties.rs, the same fields lua-meta/nodes.lua and surfaces.lua come from and the engine parses through. just stubs rewrites what sits between two markers inside the page and leaves the rest alone:

<!-- Generated from renderer/src/lua/nodes/properties.rs by `just stubs`: edit the table there. -->
<!-- End of the generated table. -->
ColumnFrom the field
TypeIts Rust type’s LuaCATS, as the stub declares it; range after it
Defaultabsent
BehaviourThe Book: paragraph of its /// block, which may link relative to the page, else the rest of that block without (ADR-NNNN)

A page links to the table’s anchors, not to rows. Prose about a property goes around the table, or into its row if every page showing it should say it.

A table a property takes, such as Edges or Transition, is declared with lua_shape! on the struct its parser reads it as (renderer/src/lua/luacats.rs): its /// blocks are what lua-meta says about the shape and each key. The struct fixes the key names and field types; which keys are optional and how Lua spells one (as Option<T>, as S) are marked by hand beside them. One struct has one shape, so anchor_rect, whose x and y may be left out, is declared as region’s Rect with every key required. The book describes a shape in prose on the page.

Page shape

One or two sentences on what the page is for, a small complete example, reference tables, a How do I… table (Task | Answer), a Gotchas table (Trap | Fix), See also, then a Source: line of code links. Each fact lives on one page; link to it elsewhere. Links to code go out of docs/ as relative paths (../../renderer/src/... from docs/<dir>/x.md); the book turns them into GitHub links and fails the build on a missing target.