how it works
The event log
Every move a user makes on a Leaf page, and every message, report, and version
the agent adds, is one JSON record appended to the page's
events.jsonl, and nothing is ever removed from it. A widget's
state is the authored markup plus a fold of that log: at each
[widget, unit, verb] coordinate, the latest action still standing
wins.
This page explains the mechanism. The rules themselves live in the events contract and the package contract's User state, which agents and package authors read.
Leaf owns the log, and a package owns its verbs
A widget sends a verb. Leaf checks it at the one append door, stores it, and folds the log back into the state the widget draws. Nothing in Leaf's server or runtime names a package's verbs.
flowchart LR
Widget["Widget"] -- "verb" --> Door{"Append door"}
Door -- "record" --> Log[("events.jsonl")]
Log -- "fold" --> Fold["State"]
Fold -- "renderState" --> Widget
| Layer | What it holds | Owner |
|---|---|---|
| Log | One record per line, append-only: messages and thread moves, actions and reports, undos, versions and sign-offs, requests and receipts, and bookkeeping such as reads | Leaf |
| Event |
The command the sender wrote, plus the author,
id, and time the door adds, and a widget event's
meaning
|
Leaf |
| Verb |
A detail schema, a fold unit, and how the state reads in
markup, declared under x-state
|
Package, in registry.json |
| Widget | Sends verbs, and draws the folded state, unset values included | Package, in its module |
Try it: each gesture lands as one event
The specimen below is a complete Leaf page with a log of its own, and its feed
lists that log newest first. Click into the specimen, type
After the canary is healthy into Another option, and press
Enter: the feed gains an add and then a pick. Pick
Later. Then press z three times. The first press brings
your own option's pick back, the second leaves nothing picked while your
option stays, and the third removes the option. Reset starts the specimen
over.
When should this ship?
A verb is a registry declaration
This is everything lf-options tells Leaf about its three verbs
and when its question counts as answered.
"x-state": {
"choose": {
"detail": {"type": "object", "properties": {"options": {"type": "array", "items": {"type": "string"}}},
"required": ["options"], "additionalProperties": false},
"unit": "widget",
"record": {"kind": "attribute", "attr": "chosen", "value": "options"}
},
"add": {
"detail": {"type": "object",
"properties": {"option": {"type": "string", "pattern": "^[a-z0-9][a-z0-9-]*$"},
"text": {"type": "string", "minLength": 1}},
"required": ["option", "text"], "additionalProperties": false},
"unit": "option",
"creates": {"child": "lf-option", "words": "text"}
},
"answer": {"detail": {"type": "object", "additionalProperties": false}, "unit": "widget"}
},
"x-awaits": {
"answered": {
"choose": {"when": {"multiple": [false]}},
"answer": {"when": {"multiple": [true]}}
}
}
action.choose detail must satisfy at the
door.chosen on each picked
option. The agent writes it into the next version, and
version check refuses a version that contradicts it.detail.option, so each added option
stands on a coordinate of its own.lf-option into the group, with
detail.text as its words.answer is the Done press that closes a
multiple group.multiple one.
An action's coordinate is its widget, its unit, and its verb.
unit: "widget" means the widget itself; any other value names the
detail field that holds the unit. A pick therefore lands on
[ship, ship, choose], and adding an option lands on
[ship, <new option id>, add]. Two actions compete only when
their coordinates are equal, so a pick never displaces an added option.
Every verb the kernel and the bundled packages declare
| Widget | Verb | Unit | Record in markup |
|---|---|---|---|
lf-suggestion |
decide |
widget | none |
lf-options |
choose |
widget | chosen attribute |
lf-options |
add |
option | creates an lf-option |
lf-options |
answer |
widget | none |
lf-board |
move |
card | position in an lf-column |
lf-draft |
edit |
widget | body |
lf-diff |
review |
file | none |
lf-playground |
choose |
widget | none |
lf-swipe-deck |
swipe |
card | position in an lf-swipe-pile |
lf-targeting |
submit |
widget | none |
lf-visual-review |
review |
case | none |
lf-task |
status |
widget | status attribute, written by the agent |
lf-agent |
state |
widget | state attribute, written by the agent |
The fold keeps the latest standing action at each coordinate
These are the specimen's gestures above, made in Chrome against a copy of the specimen's page. The group shows the fold of whatever still stands after each one.
| Gesture | Coordinate | The group shows |
|---|---|---|
| Enter in Another option |
add on the new option, then choose on
ship
|
Now, Later, and the new option, picked |
| Pick Later | choose on ship |
Later picked. It supersedes the first pick; the added option stays, because it stands on another coordinate. |
| z | undoes the pick of Later | The new option picked again, since the pick it superseded stands |
| z | undoes the first pick | Nothing picked; the new option is still there |
| z | undoes the add |
Now and Later, nothing picked |
leaf events printed the log below, one record per line; here each
record is broken over a few lines. The first line of the log, at
seq 1, is the note that published the page's first
version. The browser minted the added option's id and each
attempt.
{"kind": "action", "revision": 1, "widget": "ship", "action": "add",
"detail": {"option": "ship-option-1f82bf7ba6828718a418aa6076e29c63",
"text": "After the canary is healthy"},
"attempt": "1f82bf7ba6828718a418aa6076e29c63", "author": "user",
"meaning": {"scope": "page",
"unit": "ship-option-1f82bf7ba6828718a418aa6076e29c63",
"depends": ["ship", "ship-option-1f82bf7ba6828718a418aa6076e29c63"],
"creates": "lf-option"},
"id": "f541943a", "ts": "2026-09-23T22:09:53-07:00", "seq": 2}
{"kind": "action", "revision": 1, "widget": "ship", "action": "choose",
"detail": {"options": ["ship-option-1f82bf7ba6828718a418aa6076e29c63"]},
"attempt": "d10bc95f51e47796494c2aa01b7c872f", "author": "user",
"meaning": {"scope": "page",
"unit": "ship",
"depends": ["ship", "ship-option-1f82bf7ba6828718a418aa6076e29c63"],
"answer": null},
"id": "c47d1659", "ts": "2026-09-23T22:09:53-07:00", "seq": 3}
{"kind": "action", "revision": 1, "widget": "ship", "action": "choose",
"detail": {"options": ["ship-later"]},
"attempt": "26e7a30143df35cda03ec8b7b164f2b6", "author": "user",
"meaning": {"scope": "page", "unit": "ship", "depends": ["ship", "ship-later"], "answer": null},
"id": "0450d79c", "ts": "2026-09-23T22:09:54-07:00", "seq": 4}
{"kind": "undo", "undoes": "0450d79c", "attempt": "abec07d894a0444733756c659d4edafe", "author": "user",
"id": "6d9e1f06", "ts": "2026-09-23T22:09:54-07:00", "seq": 5}
{"kind": "undo", "undoes": "c47d1659", "attempt": "5a3b1fb07f32ba1933b472b288a03e36", "author": "user",
"id": "935db19d", "ts": "2026-09-23T22:09:55-07:00", "seq": 6}
{"kind": "undo", "undoes": "f541943a", "attempt": "7d324c8ea06016e59db0ca09ea0303e4", "author": "user",
"id": "b0f9f750", "ts": "2026-09-23T22:09:55-07:00", "seq": 7}
kind, revision,
widget, action, detail, and
attempt, its retry key. The door stamped the rest.meaning and refuses a sender that supplies
it. It keeps only what a reader without the registry could not recover:
whether the action was made on the page or in a thread (scope),
its fold unit, the element ids it depends on, and
the child tag it creates.id; seq is the
record's line in the log.null means it closes no thread; a group with a
resolves attribute would name one here.Three things take an action back, and none of them deletes it
- A later action at the same coordinate supersedes it, as the pick of Later did. Undo the later one and the earlier one stands again.
-
The user's
undowithdraws one gesture of their own: an action, a resolve or reopen, or an approval. Messages and requests stand once sent. -
The author's
restated: a stamped version lists element ids it deliberately rewrote, and every earlier action that depends on one of them stops standing.
Once the agent writes a standing decision into the markup, such as the added
option as an ordinary lf-option with the same id and words, the
document carries it and the action no longer changes what the page shows.
An agent's state moves through the same fold
A verb that declares "writer": "agent" is the agent's:
leaf report appends a report at a coordinate like an
action's, and it stands until a stamped version answers it. The package
contract's
User state
gives the whole rule.
A program can follow the log as it grows
leaf events PAGE --follow prints every stored record and then
each one the door admits as it lands, and a reader that restarts with
--after the last seq it printed picks up where it
stopped.
Typed external data under data/ is replaced in place rather than
logged, so the feed does not carry it. The events contract's
Following the log
gives the feed's whole contract.
Where each rule is written
-
Events and conversation: every event kind, undo, admission and
meaning, and the change feed. -
User state: how a package declares
x-stateverbs,x-awaits.answered,creates, and agent-written verbs. -
Registry keys: the exact definition of
x-state,x-awaits, and the keys beside them. - Layer composition: which recorded actions a re-vendored layer must still be able to read.