How it works
How Leaf works
An agent builds a page around the work. The user comments, explores, and makes choices. The agent responds by revising the same page.
Leaf supplies the interface and the record of that exchange: comments attached to passages, interactive widgets, decisions, and versions. The agent supplies the content and does the work, using its existing tools in Claude Code, Codex, or Pi.
In this topic
Pages, core, and packages
A page is the document the user reads and acts on. Its HTML includes widgets such as choices, charts, and boards. A passage identifies the words a comment concerns.
Leaf’s core supplies shared state, events, and page lifecycle. Packages contribute widgets, domain behavior, styling, and authoring instructions. A page selects packages and a theme; Leaf combines those with its core declarations and assets into the page’s layer.
flowchart TD accTitle: How Leaf composes a page accDescr: Leaf core and selected packages compose the layer. The authored page runs with that layer and the serving Leaf's browser runtime. Core["Leaf core"] Packages["Selected packages<br/>and theme"] Layer["Composed layer<br/>Registry, theme, widgets and modules"] Browser["Authored page<br/>Running in the browser"] Core --> Layer Packages --> Layer Layer -->|With Leaf's browser runtime| Browser
The layer’s registry gives agents, checks, Python, and the browser a shared description of its elements and capabilities. It declares valid markup and the meaning of widget actions; widget modules implement their behavior and presentation.
Responsibilities and state
| Part | Responsibility |
|---|---|
| Agent and harness | The agent writes content and answers updates. Its harness—Claude Code, Codex, or Pi—runs its turns; Leaf’s harness integration delivers page updates to it. |
| Leaf core | Python admits events, appends the log, and computes shared state. The browser runtime presents that state with authored defaults and the tab’s pending gestures. |
| Packages and layer | Packages define domain behavior and presentation; their composition supplies the page’s vocabulary and appearance. Extending explains how to implement them. |
| Page directory | Keeps authored content, package and theme selection, data, media, captured revisions, and the event log. Page files and history describes this record and the machine-local claims and delivery records kept outside it. |
Authored content starts the page’s state; the append-only log records changes. Each reading applies the events still in force. Undo records which gesture it withdraws, leaving the original event in history. Focus, scroll, selection, and open disclosures remain browser-local interaction; an unsent draft can be recovered from browser storage.
The Leaf serving, checking, or exporting a page supplies its current runtime and composes its layer from the recorded selection. Historical revisions keep authored content and recorded decisions, while the serving Leaf supplies the code that draws them.
The exchange
An agent proposes queue policies for an importer. A choice or comment the user sends is an update. A comment opens a thread, the conversation about its passage, widget, or the page as a whole. To make this choice yourself, try the queue policy practice in Workflow.
- The user chooses “Pause the producer”. The browser draws that choice immediately. The server validates the action against the registry and appends its meaning to the event log. The server’s next reading supplies the choice’s effect on obligations and work owed.
- Leaf delivers the update. The harness brings the choice and its context into the agent’s session. Its workflow tracks delivery and work, distinguishing receipt from the agent explicitly starting it.
- The agent incorporates the decision. It revises the proposal to use backpressure and stamps a version that records the choice in the markup. A revision captures authored content; a version names a revision the user can revisit. If the update was a comment, the agent answers in its thread.
An obligation records an input or answer owed and whose turn it is: first the user’s queue-policy choice, then the agent’s incorporation of that choice. A to-do records work the agent owes, such as measuring the importer’s throughput. Obligations and to-dos supply the On me and On agent lists; threads keep the conversation, and workflows track the handling of updates.
The same link shows the revised proposal. Stable widget ids keep recorded
choices attached across edits. When an edit changes what a decision was
about, the author explicitly marks it restated and explains
why the decision no longer applies.
State and records defines these concepts;
Revisions explains their continuity.
Find the right guide
| Guide | Use it to understand |
|---|---|
| How it works | Core concepts and how their mechanisms fit together: workflow, shared state, revisions, progress, and the browser runtime. |
| UI | Page content, layout, styling, navigation, and interaction. |
| Extending | Implementing widgets, themes, and packages. |
In this topic
Author, hand over, and respond
The harness supplies the agent’s tools and runs its turns. This example follows a queue-policy review from the first page to the answer. Command inventory lists the available operations; Harness connections explains delivery and receipt.
Publish a page
The agent reads Leaf’s skill and the reference for each step: page authoring before writing, and its harness contract before handoff. These instructions set goals for the user’s experience and leave wording and presentation to the agent. Agent instructions and notices lists every source of instructions and when the agent reads it.
The agent creates a page directory and writes
index.html with ordinary HTML and Leaf elements. Here,
./review names that directory, relative to the commands’
working directory.
leaf page init records the page’s package and theme
selection. The Leaf serving the page supplies its runtime and composes
the selected packages. The agent authors
its content using the HTML and Leaf elements described in
UI.
leaf server start starts the page’s HTTP server and claims
the page for the current agent session: that session receives its
updates. The command returns a URL with an access key. The agent hands
over the URL and ends its turn; the harness integration continues
watching.
leaf page init --package diagram ./review
# Write ./review/index.html with the harness's file tools.
leaf page check ./review --render
leaf page stamp ./review --text "Queue policy ready for review"
leaf thread create ./review --obligation --text "Which queue policy fits the importer?"
leaf server start ./review
This example is a record meant to be revisited, so the agent reviews and
stamps it before handoff. A quick page needs only
leaf page check. Revisions defines
valid saves, stamps and version addresses;
Checks before a revision goes live describes
validation.
Resume an existing page
To resume an existing page, the agent reads its active HTML and
leaf page state ./review, which returns current state as
JSON. leaf page claim ./review registers the current
session as the recipient of the page's updates without starting its
server. Starting the server also makes that claim.
Answer updates
A reaction records brief feedback on a passage or agent reply without opening a thread. The default reactions request keeping, changing, clarifying, shortening, supporting, or prioritizing content. Packages can declare additional reactions.
A browser comment or choice sends an HTTP request. Leaf validates it,
appends it to the event log, and delivers it to the agent with thread
context and instructions assembled from the registry. The harness
supplies this JSON batch or an id to open with
leaf delivery read.
Choose a policy, then open Show activity below it to see the recorded choice. Open the page’s leaf menu and choose Undo; press Activity changed in the feed to see the choice marked undone. This example uses the page’s activity log and Undo; its queue policy is fictional.
Importer queue policy
The importer receives bursts of records faster than it can process them. Its queue has room for 1,000 records.
When the queue fills, what should happen?
Recorded actions
For a comment requesting a policy change, the agent follows the delivery’s receipt instructions and first posts a progress message naming the work. This also starts the event, so the user sees which update it is handling. It then reads the current page and thread, revises the policy, and replies. This example posts replies through the CLI; harness routes describes when Leaf captures a turn’s messages instead.
leaf thread reply <answer.ref> --ephemeral --text "Checking the importer's queue policy."
leaf page state ./review
leaf page state ./review <thread-id>
# Revise ./review/index.html.
leaf thread reply <answer.ref> --done --text "The importer policy is updated."
leaf thread create ./review --obligation --text "Does the revised policy match the workload?"
A comment needs a thread reply. A widget that asks, any widget whose
registry entry declares x-awaits, creates an
obligation on the user’s On me list, with a prompt, typed
answer, and lifecycle; an optional lf-ask supplies its
heading as the prompt. The widget owns the value, and the agent records
the decision in a stamped revision. The browser fetches the revised HTML
and state.
Example: answer a page choice
The page asks which queue policy to use with
lf-options id="queue-policy" choose. When the user
chooses Drop the oldest record, Leaf delivers that choice to the
agent. The agent starts the choice event, reads the current choice,
and revises the HTML to show the selected option:
<lf-options id="queue-policy" choose>
<lf-option id="drop-oldest" chosen>Drop the oldest record</lf-option>
<lf-option id="block-producer">Pause the producer</lf-option>
</lf-options>
After checking and reviewing the result, the agent stamps the revision to record the decision. The stamped markup answers the choice event and ends its start:
leaf task start ./review <event-id> --text "Recording the chosen queue policy"
leaf page state ./review
# Add chosen to the picked option in ./review/index.html; check and review.
leaf page stamp ./review --text "Queue policy: drop the oldest record"
Delivery fields and answer semantics
In a delivery requiring a thread reply, answer.parent is
the message it threads under. The event's id identifies
the update to start, and answer.responds identifies the
update the reply answers. The
example batch uses
9d41b20f for all three because the comment opens a new
thread. A later message has its own event id within that thread.
A user comment requires a reply in its thread. A decision on a page widget requires a stamped revision. If the obligation's widget declares a record form, the revised HTML uses it to show the decision, such as the chosen option. An obligation embedded in a thread message receives a thread reply, because that message's HTML is fixed. Resolving a thread or moving a card can require no answer; the agent still receives the event. A multi-select obligation submits the choice when the user presses Done.
A delivery is one JSON document, whose format field names
its version. Its envelope contains one or more page batches,
preserving the captured event order, thread context, and handling
instructions. A delivery id supplied by the harness names this whole
envelope; an event’s id names one recorded event.
Harness connections and receipt describes how
to read and confirm a delivery pointer.
Data structures lists
every field.
| Reading | Meaning for the agent |
|---|---|
acknowledge |
The instruction for confirming receipt, or
null when the harness confirms it.
|
page, through_seq |
The batch's page and captured log position. |
subject, threads, says
|
The affected page, thread or widget, its discussion context, and authored words for named gesture targets. |
answer |
The response owed at capture, with its address and
ref: a thread reply, whose
writer is the agent or the turn’s final message, or
a stamped markup revision.
|
Event handling |
Ordered clause ids to read in the batch's
handling object.
|
The registry is Leaf's machine-readable definition of elements and
events. It declares event instructions under
$events.handling and response instructions under
$events.answering. Leaf combines the applicable clauses
in the batch's handling object. The agent follows those
clauses and reads current state before answering: a newer update may
already have settled the response the captured batch requested.
{
"handling": {
"h1": "Read the thread context before answering.",
"h2": "Write the answer with leaf thread reply using its answer.ref."
},
"events": [
{
"id": "9d41b20f",
"kind": "comment",
"handling": ["h1", "h2"],
"answer": {
"kind": "reply",
"parent": "9d41b20f",
"responds": "9d41b20f",
"ref": "6ba31df0:0:9d41b20f",
"writer": "agent"
}
}
]
}
The event's list selects h1 and then h2 from
this batch. These ids belong only to that batch. The event id
identifies the update being answered.
Change the live page
The agent can save HTML, replace a widget’s data source, or report a widget state change while the user reads. The browser fetches the revised document and state when the server reports a change (Freshness checks and state reads).
leaf data set ./review queue-metrics --file ./metrics.json
Here, the declared source queue-metrics receives the
complete value in metrics.json. For a widget state change,
leaf page report records its declared operation and fields.
Saving HTML changes the live document; stamping adds a version address
and changelog entry.
These changes preserve the user's recorded choices. If an edit changes
what a decision was about, the author marks the widget
restated to invalidate that decision. The
Revisions view explains how decisions survive
page edits.
Threads explains anchors and how they survive revisions, drawings, titles, summaries, and the Threads list.
Source errors
A source error can leave the file on disk newer than the live page (Checks before a revision goes live). Read leaf page state alongside the active HTML to
check which revision the user sees.
Wait for updates or finish
leaf page open ./review leaves the page available for
feedback. Questions that need an answer go in thread messages with
--obligation, which puts them in On me. The harness
integration keeps watching after the turn ends and sends later updates
back to the same session.
leaf page close ./review marks the page finished and stops
watching it. Leaf refuses it while an update awaits receipt or an
answer, or an agent to-do is open. If the agent ends a turn with a
delivered update unanswered, the Stop hook tells it so (Stop hook checks).
Background work and durable to-dos
Work longer than a few minutes runs in background workers, whose results start another agent turn.
leaf task start names the update or to-do the agent is
handling in this turn, and a reply or stamped decision answers the
update. Work that outlasts the turn, or that no update asked for, gets
a to-do, which stays open across replies, revisions and sessions until
the agent ends it. The agent opens it on a thread, a widget that takes
to-dos, or the whole page, and starts it with the id
leaf task create prints:
leaf task create ./review page --title "Check the importer policy"
leaf task start ./review <todo-id> --text "Checking the importer under load"
# Run the check and put its result on the page.
leaf task end ./review <todo-id> done --text "The load check is recorded on the page"
A to-do ends as done, failed, or
dropped, with a note saying where its result is or why
there is none. A stamp with --completes <widget-id>
ends every open to-do on that widget as done. Until then the to-do
stays on the agent's list, On Claude.
Stop hook checks
When the agent ends a turn, Leaf’s Stop hook checks its pages for
unanswered updates and delivery between turns. Claude Code shows a
reminder as context, not an error. A new update normally opens a turn
of its own after this one ends, preserving this turn’s final message
as its answer. Where nothing watches afterward, as with Claude Code’s
plain
--print or a Codex task waiting with
leaf wait, or where the next turn would open on a message
in the user’s name, as with Codex’s codex queue, an
update needing an answer enters the current turn instead. An update
needing no answer waits for the next turn.
flowchart TD accTitle: Checks before a turn ends accDescr: Check in order, taking the first match. A new update needing an answer with no later watcher is delivered into this turn. Otherwise an already extended turn ends. Otherwise an unanswered update not started this turn, or an open page with no watcher, continues the turn. All other turns end. New["New answer needed<br/>No later watcher<br/>Deliver update; continue"] Extended["Turn already extended<br/>End turn"] Pending["Delivered update unanswered<br/>Not started in this turn<br/>Name update; continue"] Watching["Page open, no watcher<br/>Arrange delivery; continue"] End([End turn]) New -->|otherwise| Extended Extended -->|otherwise| Pending Pending -->|otherwise| Watching Watching -->|otherwise| End
- An update started in this turn lets the turn end. Long work runs in background workers whose results start a later turn, so the Stop hook doesn't hold a turn open for an update the agent started in it. The update reads On it, with the agent's line beside it, until the next turn answers it or starts it again; otherwise it soon reads Gone quiet.
-
Between turns, a page needs a watcher: Claude
Code's or Pi's watch hook, a
leaf wait, or Codex's adapter. Without one, the user's next update waits in the event log until the agent's next turn, which in Claude Code Leaf starts by messaging the session. A closed page or a developer's preview needs no watcher.
Agent instructions and notices
The workflow, including what the agent records for the user, lives in the skill, which the agent reads at the start and again after a compaction. A rule for one element or one kind of update travels with it, so the agent reads it when it acts. Problems the agent can't see for itself, such as an update it hasn't answered, arrive from Leaf's hooks.
| Source | What it tells the agent | When the agent reads it |
|---|---|---|
| Leaf skill | The goals for the user's experience, and a reference for each step: authoring, handoff, answering updates, revisions. | When a request or a delivery matches the skill's description, then each reference before its step. After a compaction, the session-start hook asks an agent holding a page to read the skill again. |
| Package instructions | How to use a package's elements and data sources, written for an audience: the page's author, or one the package names, such as the monitoring package's coordinator. |
When the agent runs leaf page instructions, which
prints each package's instructions/ files by audience.
Each --use adds that entry's
x-instructions and its data contracts' instructions.
|
| Delivery handling | What each delivered update asks: the answer owed, where to write it, and the registry's clauses for that kind of update. | In every delivery, which lists each clause once and has each event name the clauses that apply (Delivery fields and answer semantics). A hook adds the delivery to a running turn, or a watch or message starts a turn with it (Harness connections and receipt). |
| Hook notices | An owed update not yet answered, input not yet picked up, a page holding input with nothing watching it, or a page whose server did not start again. | When the agent ends a turn (Stop hook checks), as a session or prompt starts, and in Claude Code between tool calls. |
| Turn-end check (experimental) | Each question it asked in a thread, a step included, with its words, its id and how to withdraw it, each stale passage it fixed, with the old and new words, and what else the fork found out of date, with the words it rests on. | In Claude Code, at the next prompt or tool call after a turn ends (Turn-end check in Claude Code). |
| Command results | A refused save, reply or event with the reason and the fix, a failed check, or current state. | In the output of the command the agent ran. The check that owns a rule reports it. |
leaf page instructions ./review --use lf-diagram | jq -er '.author'
This prints how to author a diagram. The repository scores each change to the skill and the packages' instructions with evals: cases a headless agent completes in Claude Code and Codex, run on the old and the new instructions.
Agent command inventory
The table groups Leaf's commands by what the agent uses them for, with
arguments omitted. Running leaf alone lists them all, and
each command's --help gives its syntax. The examples above
show commands for publishing a page and answering updates.
| Purpose and result | Commands |
|---|---|
|
Prepare a page Select packages, read their authoring instructions, and copy media into the page. |
leaf page initleaf page instructionsleaf page media
|
|
Check and publish Validate the source, record a revision, or produce an offline snapshot. |
leaf page checkleaf page stampleaf page export
|
|
Read page evidence Read current JSON state or raw JSONL events. These reads do not confirm receipt of input. |
leaf page stateleaf page events
|
|
Receive updates Read a delivery, confirm receipt when its transport requires it, or wait for updates directly. |
leaf delivery readleaf delivery ack
leaf wait
|
|
Declare work and report progress Open or close a page for feedback, or report a widget's declared state transition. |
leaf page openleaf page closeleaf page report
|
|
Track work Open work that outlasts a turn or answers no update, start an update or to-do with its current operation, end a to-do with its outcome, or arrange steps in the plan. |
leaf task createleaf task start
leaf task end leaf task plan
|
|
Write a conversation Open or answer a thread, edit an agent message or title, close a thread, or summarize a range of messages. |
leaf thread createleaf thread replyleaf thread editleaf thread resolveleaf thread summarize
|
|
Publish external data Replace or remove the current value of a declared data source. |
leaf data setleaf data clear |
|
Edit a local file Let the page read and write one chosen text file on the machine serving it. |
leaf file bind |
|
Run the page server Start or stop the HTTP server, or register the current session to receive an existing page's updates. |
leaf server startleaf server runleaf server stopleaf page claim
|
|
Launch a Codex terminal Run the experimental terminal client with its own App Server. |
leaf codex launch
|
|
Extend the vocabulary Create, validate, or install a package, or run one of its scripts through stdin and stdout. |
leaf package initleaf package checkleaf package installleaf package run
|
Harness connections and receipt
Claude Code’s background Stop hook watches between turns and wakes the agent; its prompt hook puts the update into the new turn. Codex’s adapter queues the update as the next message, or starts a turn when Leaf is an App Server client. Pi’s experimental extension runs inside Pi. Each route supplies the same delivery format and says who confirms receipt.
- Confirm receipt separately from transport. Leaf counts an update as received only when the agent or harness confirms it. A harness discards output from a timed-out hook, so the hook confirms well before its deadline and passes a complete delivery into the turn only after confirmation.
-
Use a pointer when the whole delivery cannot fit or be
confirmed.
A delivery too large for the turn’s context, or one the hook could not
confirm in time, arrives as an id.
leaf delivery read <delivery-id>confirms nothing; the agent follows everynextcommand, then runsleaf delivery ack <delivery-id>as the envelope’sacknowledgefield directs. -
A direct wait confirms on its next call. A Codex task
running its own wait uses
leaf delivery ack <delivery-id> && leaf wait. Until then, the events remain available to the next wait, in case output was lost or cut off. Stages of an update names the evidence for Delivered on each route.
| Harness / route | Updates and answers |
|---|---|
| Claude Code | Hooks supply updates when a turn starts or ends, and wake the session between turns. The hook confirms a delivery it passes whole. |
| Pi (experimental) | Leaf's extension checks the session's pages as each run settles and puts the delivery in a new run, or in the current run after its tool calls. The extension confirms a delivery it passes whole. |
| Codex queue | Leaf’s adapter queues a delivery pointer for the next turn. During work, hooks pass complete inline input or a pointer. The delivery’s acknowledgement instruction distinguishes transport confirmation from complete receipt. |
| Codex App Server |
Leaf starts an agent turn with the updates and confirms receipt.
When a reply’s answer.writer is turn, its
opening posts as progress and its final message becomes the thread
reply. A reply whose writer is agent uses the reply
command.
|
| Direct wait |
Without a hook integration, leaf wait prints the
delivery in the current turn, and
leaf delivery ack confirms it before the next
leaf wait.
|
On every route the agent can answer with
leaf thread reply <answer.ref>. One task can receive
updates by several routes, and each delivery says how to confirm it and
how to answer.
Claude Code integration
leaf hook handles Claude Code's prompt and Stop hooks, and
leaf session-end releases the session's pages. A background
Stop hook runs leaf hook --watch and wakes the session when
an update arrives.
Leaf reads Claude Code's session registry to tell whether a session is
busy, waiting or idle. If nothing is watching the page, Leaf can message
the session through its socket, or resume a retired background session
with claude --bg --resume. The socket accepting a message
doesn't show that the agent read it.
Turn-end check in Claude Code (experimental)
Late in a long session, the page can drift from the conversation: the agent leaves something to the user only in its chat reply, so On me misses it, or a page goes on saying what the work has since changed. In Claude Code, when the agent finishes a reply in a session holding a page, Leaf's hooks module forks the conversation. The fork is a copy of the conversation that answers one question without calling tools, and the agent never sees the exchange. Leaf sends it the skill's turn-end check with Leaf's rule for what goes On me, as the agent reads it, and for each page its text as the user reads it, its On me, the questions already answered or withdrawn, and the items it shows under way. The fork names:
- each decision, answer, permission or step the agent left to the user that no On me item holds, such as which of two designs to keep or trying a build
- what a page still says that the work or the conversation has since changed, such as a result or a section the user dropped in chat
- an On me item the user answered elsewhere, or the work made moot
- work the agent promised or set running, such as a background job, that no page shows
Among asks, the fork leaves out what an On me item already holds, what the user has answered since, and what the agent will settle itself. For each ask it gives a thread title, a message of a few sentences that the user can answer without the chat, and the words of the passage it concerns, if one does. A step the user is to take is asked the same way: the message says what to do, and asks for a reply only for what the user alone can report, such as what a build showed them, or where the agent's next step waits on it, since the reply tells the agent to go on. Leaf skips an ask whose title a question the agent asked already has.
Leaf opens each remaining ask as a thread, its message written as the
agent's with an obligation, as
leaf thread create --obligation writes one. Each stands on
that passage, or on the page as a whole where the fork named none or the
page doesn't show its words once. It stays On me until the user replies
or settles it with a reaction, and nothing on the page marks where it
came from.
Where a few of a page's words have gone stale, the fork can give the words to put in their place. Leaf makes that change itself where the page shows the old words once: it finds the place in the page's source the user reads them from, swaps in just the words that change, checks that the page then reads as the fork asked, saves it as any save is checked, and tells the agent the old and new words, so the agent can change them back or rewrite them. A fix that needs more than a sentence, or that Leaf can't place, goes to the agent instead.
Only the agent settles an On me item or adds to its own list, so the
other findings reach it as messages. Each gives the fork's summary and
the words from the conversation it rests on, which lead the agent to the
original message even after a compaction. At its next prompt or tool
call, the agent reads these and each record and change Leaf made, and
withdraws a question it disagrees with, with leaf task end,
as it would its own.
The fork starts after the turn has ended, so neither the turn nor the user's next message waits for it. It reads the conversation from the session's prompt cache and answers in a few seconds. Leaf writes no record and changes no page once another turn has opened, since the fork's reading is then out of date; a finding still reaches the agent, naming the turn it came from. When a fork fails or its answer isn't the JSON Leaf asked for, Leaf writes nothing and records the failure in Claude Code's debug log.
Codex entry points and the public website
leaf server start connects the page to the current Codex
task. leaf page claim ./review connects delivery when
claiming a page without serving it. The desktop app runs an App Server
Leaf can't reach, so a chat a window has opened takes each update as a
turn the app starts, with no message in the user’s name. Any other chat
uses the queue route.
leaf codex launch starts a terminal client with a private
App Server and sets LEAF_CODEX_APP_SERVER for the task.
Leaf can connect to that endpoint; this terminal route is experimental.
On leaf.page, the website host starts a Codex task over App Server for each page a visitor changes. The hosted agent answers deliveries on that page rather than starting a new one. The website describes how the site serves pages.
Owning contracts
The delivered event batches reference owns the envelope and handling lookup. Conversation loop owns to-dos, starts, statuses, and the response loop.
Harness routes are specified in Claude Code, Codex, and Pi. Session lifetime owns the hooks, adapters, and App Server protocol behind them. Page storage owns revisions and browser replacement.
In this topic
Current state
The active revision's HTML supplies initial values. Leaf applies
recorded actions to those values on each read, keyed by
[widget, unit, verb]: the widget, the part of its state
being changed, and the operation. The latest action still in effect wins
each key. leaf page state reports the result; the browser
adds pending browser updates.
Every event enters the log through the same validation. Leaf checks widget actions against the registry and records their meaning at admission, so later readers need no surviving widget to interpret them. Undo appends an event identifying the action withdrawn. The event log reference follows a choice and undo through this computation.
Records
The page directory holds the durable record. The server derives threads, obligations, workflows and attention from it; the browser combines that reading with authored values and pending updates. The table distinguishes stored facts, computed records and browser state.
| Kind | What it is | Identity |
|---|---|---|
| Recorded in the page directory | ||
| Revision | An immutable capture of valid HTML and its dependencies. The active revision supplies each widget's initial state; a stamp gives a revision a public version number. |
r4-… names a revision; v2 names a stamp
|
| Event | One recorded transition: a comment, reply, reaction, widget action, undo, pickup or to-do. The browser and the agent's commands write events through the same validation step. |
its event id; seq gives its position in the log
|
| Data source | The latest value of one external feed, replaced whole. | its name |
| Folded by the server on each read | ||
| Thread | One conversation, anchored to a passage, a widget, or the page as a whole. It holds its messages, an optional title, its attention, and which agent messages the user has not read. | the id of its opening message |
| Message | A user's comment or reply, an agent reply or progress message, or a reaction. An edit revises the same message; several messages can belong to one conversation turn. | the id of the event that wrote it |
| Workflow | An unsettled update's delivery and work stage: Queued for Claude, Up next, Delivered, On it, Replying or Answered. Conditions report interruptions or failures. The workflow names its thread and owed answer, if any. | the update's event id |
| Obligation | An input or answer owed by the user or agent. It holds its source, typed answer, next actor, and status: open, answered, answered elsewhere or withdrawn. |
widget:, message: or
update:, followed by its source
|
| To-do | Work the agent owes until it ends. One on a thread keeps that thread waiting on the agent. | its event id |
| Attention | Whose move a thread waits on, and why: the user, for an obligation or an update to send again; the agent, for a workflow or a to-do; or neither. | one per thread |
| On me, On agent, Done | The user's list, the agent's list (On Claude, say), and what has left them, selected from the obligations, to-dos and workflows. | one selection per reading |
| Combined in the browser | ||
| Publication | The one read-only reading of current state: the authored initial values, the server's latest reading, and the pending browser updates. | its epoch number |
| Presentation proof | Which parts of the page have drawn which epoch. | one per renderer and epoch |
| Browser-owned state | Focus, scroll, navigation position, open disclosures and unsent drafts. They stay outside shared state; browser stores preserve some across reloads (Data lifetime). | the element, or the draft's thread |
Relations
The diagram connects the records above to the work the interface shows. A multi-pick choice still being composed has no workflow until its finishing action; On me and the agent’s list defines list membership and completion.
flowchart TD
accTitle: Work records determine attention
accDescr: An update has a workflow, an answer request creates an obligation, and the agent's promise or task creates a to-do. These records determine whose move is next and which work is finished, supplying thread attention and the page's work lists.
subgraph Records["Record creation"]
direction LR
Update["Update"] --> Workflow["Workflow"]
Request["Answer<br/>request"] --> Obligation["Obligation"]
Promise["Promise<br/>or task"] --> Todo["To-do"]
end
Records -->|records| Reading["Whose move is next?<br/>Which work is finished?"]
Reading --> Views["Thread attention<br/>On me · On agent · Done"]
Shared readings
The browser runtime combines the server’s reading, authored values and pending updates. Shared selectors supply names, statuses and identities to the margin, Page Map, Leaf panel, activity feed and widgets. Thread readings defines the conversation selectors; the runtime owner table identifies each reading’s owner.
A surface chooses placement and presentation, using these shared readings for meaning. It never infers state from another surface's rendered text. Drawn elements carry their identities so a press can find the thread or widget it acts on.
Page files and history
A page directory holds its authored HTML and modules, package and theme selection, revisions, event log, media, and external data. Packages selected by a local path are copied into it; the serving Leaf supplies bundled packages and the runtime. Copying the directory preserves the authored content and recorded decisions. Session ownership and browser drafts live elsewhere. The diagram shows the durable inputs to current state.
flowchart TD
accTitle: Durable inputs become current state
accDescr: Agent edits become revisions, accepted actions append to the event log, and data publications replace values. Those durable stores supply captured markup, recorded actions and external values to the shared reading used by the browser and page state command.
subgraph Stores["Inputs and storage"]
direction LR
Edits["Edit"] --> Revisions["Revisions"]
Actions["Action"] --> Log["Event log"]
Publish["Data set"] --> Values["Values"]
end
Stores --> Inputs["Captured markup<br/>Recorded actions<br/>External values"]
Inputs --> State["Current state"]
State --> Readers["Browser<br/>leaf page state"]
revisions/ and
validates gestures and commands at the same entry point before
appending them to events.jsonl. Any process can publish
whole values with leaf data set. These stores travel with
the page directory. View state and drafts remain in the browser;
session claims and delivery coordination remain in the machine’s state
home.
Leaf also keeps records for live harness activity, browser presence, delivery and diagnostics:
| Where | What it tracks |
|---|---|
user-views.json |
Each open tab's view of the page, and the last time a tab showed it |
stream.json |
What the agent's harness observes of a turn as it runs: its activity and the reply it is drafting |
observations.jsonl |
A diagnostic trace of requests and browser interactions |
| the machine's state home, outside any page | A shared directory for session claims, delivery batches, neighboring page activity summaries, and the machine's access key |
Data structures lists the fields of each of these records, of every event kind, and of the state the server sends the browser.
External data
External values, such as benchmarks or build status, use one JSON
publication per named source. leaf data set validates the
value against the page’s declaration, records the contract beside the
value in the source file, and replaces the publication atomically. Any
process can call it. Leaf validates the value against the recorded
contract on read and displays a source error for a mismatch.
A source replacement updates the open page without creating a revision;
each publication holds its latest value. A later page revision may bind
the source to another contract, which its next write takes. An
lf-num instead freezes a number into authored text. It
retains that number when the source changes, and
leaf page check reports when its source has changed.
Data lifetime
Accepted events and external data survive closing a tab or restarting the server. Browser stores preserve drafts and interface arrangements with the scopes shown below. An update the server has not yet accepted has not reached durable storage; while the status says the connection is lost or the update is not through yet, keep its tab open until it is.
| What | Reload | Close the tab | Server restarts | Another browser |
|---|---|---|---|---|
| Picks, drags, and comments accepted into the log | ✓ | ✓ | ✓ | ✓ |
| External values, until the next write replaces them | ✓ | ✓ | ✓ | ✓ |
| Words the user has typed and not sent | ✓ | ✓ | ✓ | ✗ |
| Arrangements of Leaf controls | ✓ | ✓ | ✓ | ✗ |
| Reading position, the open tab or panel, design mode | ✓ | ✗ | ✓ | ✗ |
Draft text is shared between tabs showing the same page; arranged controls are shared across pages in that browser. Reading position, the active tab or panel, and design mode belong to one browser tab. Comment text awaiting acceptance remains in the draft store; pending widget actions have no durable browser queue.
In this topic
Revisions
When Leaf reads changed, valid page source or dependencies, it captures an
immutable revision and makes it live. Identical captured inputs reuse a
revision. leaf page stamp adds a changelog entry, a version
number and a stable address such as /versions/v2.html. The
live URL follows revisions; a tab displaying v1 stays on v1.
An open tab installs a live revision while preserving reading position and active controls, waiting where it would interrupt the user (Live revisions). A comment follows its passage, or its surviving section if the passage disappears. If neither can be identified, the thread detaches rather than choosing unrelated text.
A request to permit an action is an ordinary decision: an
lf-options group whose options take the action its heading
and prose describe or keep things as they are, so declining is an answer
too. Its pick answers the widget's obligation on On me, and choosing it
again reopens it. Each request is independent of other unanswered widgets
and page stamps, and the pick persists with the widget's id across
unrelated edits. When the action or evidence changes enough to invalidate
that permission, the author explains the change and marks the widget
restated. A request that depends on another decision is
presented after that decision, once the action to permit is concrete.
Choose Publish the report, then choose it again. The request changes immediately, and the activity feed records the pick and its withdrawal.
Publish the sample report?
The park survey would go to the sample team's shared workspace.
Checks before a revision goes live
Before making a revision live or stamping a version, Leaf validates the
HTML structure, element declarations, dependencies, and recorded user
decisions. An invalid save keeps the last valid revision live and reports
the error. leaf page check --render additionally opens the
page in Chrome in both color schemes to check for console errors,
invisible widgets, and horizontal overflow.
Decisions across revisions
A pick or drag records a structured action, shown immediately and delivered alongside comments. Once accepted into the log, it survives reloads, other tabs, and later revisions. The event log reference shows how actions and undo determine current state.
An edit preserves recorded choices unless it explicitly changes their
basis. The agent marks an affected element restated and
explains why in the version note. Otherwise the choice still applies.
Wording, labels, placement and vocabulary can change; the log keeps each
action's original meaning.
z withdraws the user's latest action, naming that action and how long ago it was made. Where that action is out of view, or the agent has already picked it up, the first press shows it and the second takes it back. Either way it leaves the user at what it took back, so undoing a resolve opens the thread again where they had it open. Each undo appends an event naming its target, so the recomputed result survives reloads.
In this topic
Progress on an update and the page
Leaf distinguishes progress on one update from
the page’s status. A busy agent has not necessarily
started every comment. Its progress message or
leaf task start names the update or to-do it is handling and
the work it is doing. That line appears beside the relevant thread or
widget and in the panel; replies carry the results.
A session is the agent conversation attached to the page; a turn is one run within it. A watcher receives updates between turns. The illustrative sequence below follows a request to shorten a lede; its times are examples, not delivery guarantees.
- observation Queued for Claude The comment appears in its thread immediately, reading Queued for Claude until the agent has it.
- observation Delivered The hook puts the comment into the agent’s turn, and Claude Code's transcript then confirms it reached the agent. Until the agent names its work, the panel shows its leaf in the working tone and no words.
-
intervention On it The
agent posts
leaf thread reply <answer.ref> --ephemeral --text "Cutting the lede to two sentences.". The comment reads On it, and the same sentence becomes the page’s status. - intervention Each new phase gets a new line When the work moves from editing to checking, the agent starts the same update again: checking the page at phone width.
- observation Answered The revision goes live, the agent's reply reports it, and the status returns to Ready — select text to comment.
Stages of an update
A label beside each comment or widget update records its delivery and handling. Leaf changes it only on recorded evidence and clears it when the required answer arrives. Harnesses skip stages they do not report. Only On it and Answered relay an act of the agent's own, so only they read in its voice; every other label is what Leaf observed of the update, set in italic.
| Label | Evidence |
|---|---|
| Queued for Claude | No harness has queued the update or put it into the agent's turn. The label is the same while the browser is still sending it and once the server has added it to the page's event log. It names the page's agent and is set in italic, as Leaf's own words are, since the agent can't yet be saying anything about the update. |
| Up next | Codex accepted the update's delivery as the task's next message. |
| Delivered |
Claude Code and Pi. The prompt hook, or a hook
between the agent's steps, put the delivery in the agent's turn,
or the agent confirmed the complete pointer reading with
A
Codex. The turn that the queued message opens
began, or the agent confirmed a complete mid-turn reading with
Codex App Server. App Server opened the turn that Leaf started with the delivery. |
| On it |
The agent started work on the update and named the work in one line,
as a progress message in its thread or with
leaf task start, or started a to-do on the update's
widget.
|
| Replying | On Codex App Server, the turn's answer is streaming into the thread. |
| Answered (label clears) |
The answer required by the delivery is recorded: a
leaf thread reply, a turn’s final message when
answer.writer is turn, or a version whose
markup records the update. The supplied writer determines the reply
route, including within a Codex task connected to App Server.
|
| Delayed, interrupted, or unanswered | |
| Still queued for Claude | The update is still queued two minutes after the user sent it. |
| Stopped | The turn the update was delivered into ended, or was interrupted, before the agent answered it. |
| Gone quiet | The agent's start on the update is 15 minutes old, or the turn that wrote it has ended with no background work Claude Code reports still running, and the agent has not started the update again since. A Got it update reads the same once nothing shows its turn still running (no status, start, or step for 15 minutes), or once the agent is in a later turn. On Codex App Server, so does a Replying update whose stream disconnects. |
| Interrupted | Codex App Server stopped the turn before it answered. |
| Not answered | The harness gave up on the update: its turn failed, or Leaf could not confirm that the delivery reached a turn. The leaf.page website and the Codex adapter record this. |
A comment needs a thread reply; a page-widget obligation needs a version recording its answer. Resolving a thread, editing a draft, or moving a card requires no answer, but the agent still receives the event. A draft edit or card move’s label clears when a version records or replaces it; inside a thread message, it clears when the agent next writes there or the thread resolves. A choice with a Done button gets no label until Done submits it.
On me and the agent’s list
The On view gathers outstanding work across the page, using the obligations, to-dos and workflows defined in State. A status sentence alone creates no obligation.
| List | What belongs there |
|---|---|
| On me |
Open obligations: each widget that asks, on the page or in a thread
message; each agent message sent with --obligation; and
each update the user needs to send again.
|
| On Claude (or the attached agent’s name) | Updates still owed a reply or a stamped revision, and open agent to-dos. |
| Done | Each list’s finished records: answered or withdrawn obligations and ended to-dos. User decisions appear under On me; answered conversations under the agent’s list. A decision recorded in a revision finishes the widget’s record, without a duplicate update in Done. |
On the page, an item On me is answered only in the widget or thread that
asks it, and Leaf adds no control of its own to end one. A widget’s
obligation is answered by the actions its
x-awaits declaration names, such as a pick, or the Done a
multi-select choice offers as its own control. A question in a thread is
answered by the user’s reply there or a settling reaction, such as 👍. The
agent ends its to-dos with leaf task end, and records the
same way an answer the user gave elsewhere, such as in chat. Done history
records the outcome, including withdrawn obligations and failed or dropped
to-dos.
Any widget can ask, whether bundled, from a package or the page’s own, by
declaring x-awaits in its registry entry, which names the
actions that answer it. To ask about content that asks nothing, such as a
paragraph or a chart, or to leave the user a step such as trying a build,
the agent opens a thread on that content or the page with a message sent
with --obligation, and the user’s reply answers it. Nothing
goes On me that neither a widget nor a thread answers, so no item carries
a generic Done, and Leaf adds no answer to a widget beyond those the
widget declares.
A thread’s attention identifies which side it awaits, using its updates, questions, and to-dos. The page-wide lists collect that work across threads and widgets. While a thread about a decision awaits the agent, its unanswered update represents the work on the agent’s list; the decision returns to On me when it needs the user again.
What the panel's line says
The panel's comment field, while empty, is Leaf's one line of words about the page. It says one thing at a time, the first of these that applies:
| Reading | Example |
|---|---|
| An error about no particular box, until the user next types | Couldn't load v3 |
| Where words go, while the caret is in the field | Comment on the page, Reply in this thread |
| Words selected on the page, which a press on the field or c comments on | “the queue fills” |
| A notice, or a walk's position while the user stays on its stop | Updated to v4, Thread 2 of 5 |
| The mode the user is in, which the ✕ at the row's end leaves | In Draw mode |
| The page's status | running the browser suite |
With a thread open, the field is that thread's reply box. It says whose turn it is beside the leaf, such as Your turn, and Reply with the key that enters it at the line's end; a notice takes their place for its four seconds, and a walk's position follows whose turn it is.
A middle dot joins separate facts, as in In Draw mode · Thread 2 of 5, and a dash attaches a detail to the fact before it. Only words the agent wrote, such as the line it gave its work, read in its voice; what Leaf says itself, such as Stopped 3m ago, is set in italic. Words the user is writing hide the line, while screen readers still hear notices and changes of status. A press the line offers, Reply or the selected words, reads in full ink with the key that makes it, and everything else reads dimmer. The leaf and the tab's icon keep the status's colour throughout, and the leaf's menu holds the status's full sentence. A folded panel shows notices beside the leaf.
Status readings
The server combines the agent’s latest start or status with observed
session, turn, and watcher activity. Browser tabs and
leaf page state read that same result. All pages uses each
serving page’s published activity summary.
- The turn. The harness integration records when a turn starts and ends. Leaf treats it as active while its opening, a start, a status declaration, or a reported step is less than 15 minutes old. Claude Code also reports the session's activity directly, including permission dialogs and interrupted turns. An interruption can therefore mark the turn ended as soon as the user presses Escape.
-
The watcher. Claude Code's watch hook,
leaf wait, or Codex's adapter registers with Leaf while watching for updates. Leaf treats the agent as available for updates while a watcher runs. A running Claude Code turn can also receive updates through its hooks. With no watcher after a turn ends, Leaf messages a Claude Code session when the next update arrives, so the agent is available too where that message starts a turn: a background job whose worker has retired is resumed with it, and a running session takes it at once where Claude Code'scrossSessionInboundsetting isaccept. - The start. A start stops counting as active once it is 15 minutes old, or once the turn that wrote it is seen to end, unless Claude Code reports the session still busy with background work, such as subagents it started.
flowchart TD accTitle: Activity status by first matching condition accDescr: Test each rule from top to bottom and stop at the first match. Otherwise continue to the next rule. An idle page is done, a stopped owner is not on this page, active work is working, stale input is stopped or last checked in, available input is awaits, stalled input needs a terminal nudge, otherwise the agent is away. Idle["Page idle<br/>Status: Done with this page"] -->|otherwise| Owner["Owner stopped<br/>Status: Not on this page right now"] Owner -->|otherwise| Work["Work active<br/>Status: Working"] Work -->|otherwise| Stale["Takes input, start stale<br/>Status: Stopped /<br/>Last checked in"] Stale -->|otherwise| Input["Takes input<br/>Status: Awaits"] Input -->|otherwise| Stalled["Input stalled<br/>Nudge in the terminal"] Stalled -->|otherwise| Away["Status: Away"]
Leaf shows work as active when a start is still current, when the harness reports a step in an active turn, or when that turn has received input requiring an answer. The freshness rules above determine when a start or turn stops counting as active. Input is stalled if it remains queued for two minutes, or if the turn that received it ends without answering. The status doesn't date the agent's line: its hooks remind it to restate a line that has stood two minutes, and the menu's full status says when the line was written.
| Status | What Leaf knows |
|---|---|
| The leaf alone, in the working tone | The agent is working, or the user's update was delivered into its running turn, and the agent has not yet said what it is doing. The update's own label says Delivered. |
| Approval needed in the terminal | A wait for the user, for input, or for approval is a dialog in the harness's own window, so the status says to answer it in the terminal, ahead of the agent's line. A step the harness reports, such as Thinking or Using a tool, shows in the menu's full status. |
| Ready — select text to comment | The agent is taking input. An update still on its way to the agent says so on its own label, and the menu's full status counts it. |
| Stopped 3m ago | The turn that wrote a start has ended without a new start. The integration is still available for updates. Pointing at the status shows the start's line. |
| Last checked in 20m ago | The agent has not renewed its start for 15 minutes. The integration is still available to receive updates. |
| Away | No integration is currently receiving updates. Comments remain saved for the next agent turn. The message Leaf sends a Claude Code session as a comment arrives may wait there behind a prompt to accept it. |
| Not answering — nudge the terminal | No integration is receiving updates, and an update has stalled. Sending a message in the agent's terminal starts another turn. |
| Not on this page right now | The session attached to the page has exited. Another session can resume the page by starting its server. |
| Done with this page | The agent wrote leaf page close …. |
Notices
A notice stands in the panel's line for four seconds and is announced to screen readers. Simultaneous news shares one notice, such as Not answered · New on me. Some news is only announced, since something on the page already shows it: an agent message the user hasn't read, which the On me mark's dot and its margin marker show, and the user's own gesture, which the widget draws where it was made. Opening a page announces only unread agent messages, not its existing activity. The agent starting or stopping work is no notice: the status in the same line says it.
| Notice | When |
|---|---|
| I shortened the lede., Edited: …, 3 replies in 2 threads | Announced only: an agent message the user hasn't read arrives, or one is edited. One message speaks for itself in its opening words, including anything it asks of the user. |
| New on me, 2 new on me | Something new waits on the user, such as a question a reply asks or a decision a revision adds. |
| Not answered, Interrupted mid-answer | The harness reports that a response failed or was interrupted. |
| Updated to v4 | A new revision replaces the page in place. |
| Moved to Done, Undid: Moved to Done | The log accepted the user's gesture, which is announced only, or its undo. |
Where the agent's messages appear
The agent writes progress messages, statuses, replies, and version notes. Leaf adds observations about delivery and activity. These commands reach different surfaces; starting work places its line both in the panel and beside the update or to-do.
Written by the agent
-
leaf thread reply <answer.ref> --ephemeralNames the upcoming work in the user’s thread before reads or edits. It sets the page’s status and the comment’s Working label, then folds behind Show 1 progress message when the answer arrives. -
leaf task start … <id> --text "…"Names the current operation for an update or to-do, such as running the browser suite. Its line remains beside the thread or widget until answered or ended. The panel’s leaf and tab icon take the status’s colour; All pages repeats its line. -
leaf page open …Leaves feedback open and clears starts while keeping to-dos. It creates no obligation. Questions that need an answer belong in a thread message with--obligation, so On me opens the conversation. -
leaf page close …The agent has finished with the page, and the status reads Done with this page (Wait for updates or finish says when Leaf refuses it). -
leaf thread reply <answer.ref>Answers the user’s comment and clears its label.--donedelivers the result;--obligationasks the user back;--todopromises work that stays on On Claude until ended. -
leaf thread reply <page> <thread>A later message in a thread already answered, such as the result of work the first reply promised. -
leaf thread reply … --obligationOpens a question on On me and makes the thread read Your turn. A reply or settling reaction answers it at once, so On me clears without waiting for the agent. A step the user is to take, such as trying a build, is asked the same way. The agent can end it withleaf task endas answered elsewhere or withdrawn. -
leaf thread reply … --obligation message:<id>Puts a question the user's reply left standing back on On me, such as when they asked something back. It keeps its id and words, so a plan step naming it reads open again, and stands on this message. -
leaf thread create,leaf thread edit,leaf thread resolveA thread the agent opens on a passage, a message it corrects, marked edited, or a thread it closes (✓ Resolved by Claude). -
leaf page stamp --text "…"Names a version and its note in Versions. The live page updates and the panel says Updated to v4. A tab on an earlier version stays there; its version notice offers Latest.
leaf page report records a widget state change. A widget
supplied by an optional package can use these reports to show a background
worker's progress.
When the agent says what it is doing
- When an update arrives. The agent names the requested work before starting it, or answers immediately when no work is needed. Each delivery repeats this instruction beside the applicable events.
- When the work changes. A new subject or phase—reading, editing, testing, or waiting on a result—gets a new line by starting the item again. Every start names its operation.
- During long work. Background workers keep the agent free for the user’s next comment. The start names the running work beside its update; the agent renews it when a worker reports (Stop hook checks).
- When it is the user’s turn. A waiting status names the answer or decision wanted.
The conversation loop reference is the agent's copy of these rules.
The work plan
For work with several steps, the agent can publish an outline, which the On view's list of the agent's work discloses from the plan's progress and On me can group by. It shows the goal, the current step, and later steps, including those waiting on the user. A step can name an existing to-do or obligation and follow its title, owner, and state, or carry a title and state the agent maintains.
Plan commands and lifetime
leaf task plan reads the complete outline from stdin. Depth
0 is the goal; each nested step is one level deeper. A plain step’s
state is open, working, done, or
dropped.
leaf task plan ./review <<'EOF'
[{"depth": 0, "title": "Review the queue policy", "state": "working"},
{"depth": 1, "item": "<todo-id>"},
{"depth": 1, "item": "widget:queue-policy"}]
EOF
Rewriting the plan replaces its arrangement; an empty list clears it. Removing a to-do or obligation from the outline ends nothing. Work that must stay owed after the turn belongs in a to-do or obligation, which the plan references; a plain step creates no such obligation.
In this topic
Browser runtime
The browser runtime upgrades Leaf elements, draws threads and shared controls, sends updates, and keeps the page current. Its inputs meet in one read-only state.
flowchart TD accTitle: One state feeds every renderer accDescr: Authored initial values, the server's reading and this tab's pending gestures combine into one current state. Renderers select the parts they read and draw the interface. Inputs["Authored initial values<br/>Server’s recorded state<br/>This tab’s pending gestures"] --> State["Current state<br/>Values · threads · obligations"] State -->|selectors| Renderers["Widgets · threads<br/>Leaf panel · page marks"]
The server supplies actions in effect for the displayed revision, threads, obligations and activity (Current state). The browser applies them to authored values and adds only what pending gestures decide by themselves.
Loading the runtime
Startup prepares a stable page before enabling its controls. Page loading distinguishes usable controls from the arrival of saved state.
flowchart TD accTitle: Prepare the page before controls arrive accDescr: Before first paint reserve geometry and capture early keys. Then start the state request, load the registry, capture authored values and upgrade the page's elements. Reserve["Before first paint<br/>Reserve space · capture keys"] --> Prepare["Start state request<br/>Restore arrangements<br/>Load registry"] Prepare --> Capture["Capture authored values"] Capture --> Upgrade["Import widget modules<br/>Upgrade elements"]
Page loading
These stages describe the document, interface and saved state. JavaScript initialization and the state request overlap. Agent availability is independent; Progress describes whether the agent has received or started work.
flowchart TD accTitle: The interface and saved state load together accDescr: HTML and styles allow reading and scrolling. Widget initialization and the state request overlap. Comments are ready once the interface initializes; saved choices and threads are usable once both paths complete. Document["HTML and styles<br/>Read and scroll"] Document --> Widgets["Initialize<br/>widgets"] Document --> Request["Request<br/>saved state"] Widgets --> Comments["Comments<br/>ready"] Request --> State["State<br/>received"] Comments --> Ready["Saved choices and threads"] State --> Ready
While the first state request is pending, the status reads Loading comments and choices…; a failed read shows Offline. Connection failures describes retry behavior.
Pending browser updates
A pick, drag or comment enters an ordered pending list. Its result appears in the sending task, before the next frame: the option is selected, the card moves, or the comment appears reading Queued for Claude (Stages of an update). Sent words leave the input but remain in the draft store until accepted.
flowchart TD accTitle: A gesture reaches the shared record accDescr: Draw a gesture's local result immediately, send it in order, receive the accepted record and state, then release the pending entry only after that state is presented. Gesture["Gesture<br/>Draw its local result"] --> Send["Send in order<br/>POST /api/event"] Send --> Accepted["Server accepts<br/>Record + current state"] Accepted --> Presented["Present server state"] Presented --> Release["Release pending entry"]
| Condition | Browser behavior |
|---|---|
| Server unreachable | Updates send one at a time, in gesture order. The local result stays drawn while the panel reports retrying, every two seconds. Keep the tab open: pending widget actions are not a durable queue. |
| Accepted or refused | An accepted response carries the recorded event and current state. The entry leaves after every renderer showing it has drawn the server’s version. Refusal immediately removes the local result, explains why in the panel, and returns refused comment text to its input. |
Which obligation changes appear immediately?
The server computes obligation completion from the whole log. A user’s answer changes the widget and its answer in the On view immediately; the obligation leaves the list when server state arrives. Replies to thread questions take effect locally at once. Thread turns also come from the server, with local adjustments for a comment being sent or refused.
Freshness checks and state reads
flowchart TD accTitle: A changed freshness token triggers a state read accDescr: A finite news request checks freshness. A changed token triggers a state request; an unchanged token keeps the current reading without another state request. News["Check freshness<br/>GET /api/news"] -->|token changed| Read["Read current state<br/>GET /api/state"]
| When | What happens |
|---|---|
| Visible tab | Freshness checks run roughly four times a second. The token covers page-file stamps, session and watcher presence, and neighboring pages in All pages. The request also records that someone is viewing the page. Hidden tabs pause checks and catch up when shown. |
| A read is running | One state request runs at a time; changes queue one follow-up. Event responses also supply state. Log position and capture time let the tab discard older, late responses. A two-second clock refreshes displayed ages, retries failed reads and requests state when the server says activity is due to change. |
| Connection fails | A failed first read leaves authored content and local comments available; state-dependent decisions wait. Later failures retain the last reading. The panel reports the loss and retries; a successful read applies normally. A request still pending remains Loading rather than Offline. |
Drawing current state
Computing state and drawing it are separate. Widgets, threads, the obligation view, panel and page marks register as renderers. Each state has an epoch number. Renderers report when they have presented it; those reports stay outside state, so a slow or failed paint cannot change the facts.
flowchart TD accTitle: Presentation follows the DOM it needs accDescr: Page marks may add text; threads locate passages in that text; obligations may live inside those threads. These presentation dependencies establish the drawing order. Marks["Page marks<br/>May add text"] --> Threads["Threads<br/>Locate passages in that text"] Threads --> Obligations["Obligations<br/>May live inside those threads"]
A renderer may defer paint during an active drag or edit. These operations wait for presentation, not merely computed state:
| Operation | Presentation requirement |
|---|---|
| Apply server state | Count it as applied and announce changes after every part has drawn it. A paint failure is reported; accepted state stays, and the next state triggers another attempt. |
| Release pending entry | Every part drawing the update must show the server’s version first, so old state never flashes between the local result and server state. |
| Render check |
leaf page check --render waits the same way to judge
the page the user sees.
|
Browser-owned state
Focus, scroll, selection, open disclosures, unsent text and active drags live outside shared state and the log. DOM nodes hold them; Data lifetime names browser stores preserving some across reloads.
| Change | Continuity |
|---|---|
| Node replaced | The renderer’s existing identity matches its replacement. Threads keep reply text and caret; surviving widget children keep focus. |
| Composer open | Only a user action—Send, Cancel, Escape or a press elsewhere—or removal of its passage closes it. Scroll, resize, panel closure and server news leave it open. |
Live revisions
A revision’s executable identity is its registry, loaded JavaScript modules and inline module scripts. Each install pairs the revision with its corresponding state.
flowchart LR accTitle: Executable identity chooses the revision path accDescr: A revision with the same executable identity patches the document, preserving unchanged nodes. A changed identity starts a fresh document with the new executable graph and current state. Same["Same<br/>identity"] --> Patch["Patch the<br/>document"] Changed["Changed<br/>identity"] --> Fresh["Load a fresh<br/>document"]
What both paths preserve
Replaced elements with matching authored ids inherit local values differing from authored defaults: typed values, checked state, disclosures, inner scroll, focus and caret. Replacements without matching ids do not preserve those values. Reading position follows a passage, not a pixel offset. Fresh documents use a one-time tab handoff that later reloads and Back do not replay; drafts and control arrangements use separate browser stores.
Installation waits during comment or reply writing, passage selection, card dragging, a pending update’s server response, or an open version menu. Latest in the version notice installs the waiting revision immediately.
Server lifetime
The HTTP server exposes the page directory to
browsers. A session-started server stops when no live session claims the
page; a claim names the recipient of its updates. A server started from a
bare shell or with --lifetime persistent runs until
leaf server stop.
On leaf.page, a private browser session gets a hosted agent environment using the same page and event machinery (How leaf.page works).
Addresses and access
A local session binds to loopback; an SSH session uses its SSH destination. A local server's URL includes an access key shared by every Leaf page on that machine. The browser keeps it in a cookie. Anyone with the key and network access can read and act on those pages.
For an unreachable address, restart with
leaf server start PAGE --host NAME, naming a host the browser
can reach. Leaf uses existing networks and creates no public tunnel. The
serving reference
covers addresses, authentication and lifetime.
Offline exports
leaf page export PAGE -o FILE captures a stamped version, its
resources and recorded state in one HTML file. Reading controls and
page-owned code work offline; new Leaf choices, comments, Undo and thread
interactions need the served page. An agent need not be running to record
a choice on a live server. Pages containing live samples cannot export.