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.

  1. The agent prepares the page. It writes the alternatives, checks the page, and opens its link.
  2. 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.
  3. 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.
  4. 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?

Pause the producer Keep every queued record; wait for space before accepting more. Drop the oldest record Accept the newest record immediately; discard the oldest queued one.

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.

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
Checks before a turn ends. Take the first matching condition. Follow “otherwise” only when it does not apply. A continued turn is checked again when the agent next tries to end it.
  • 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 init
leaf page instructions
leaf page media
Check and publish
Validate the source, record a revision, or produce an offline snapshot.
leaf page check
leaf page stamp
leaf page export
Read page evidence
Read current JSON state or raw JSONL events. These reads do not confirm receipt of input.
leaf page state
leaf page events
Receive updates
Read a delivery, confirm receipt when its transport requires it, or wait for updates directly.
leaf delivery read
leaf 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 open
leaf page close
leaf 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 create
leaf 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 create
leaf thread reply
leaf thread edit
leaf thread resolve
leaf thread summarize
Publish external data
Replace or remove the current value of a declared data source.
leaf data set
leaf 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 start
leaf server run
leaf server stop
leaf 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 init
leaf package check
leaf package install
leaf 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.

  1. 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.
  2. 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 every next command, then runs leaf delivery ack <delivery-id> as the envelope’s acknowledge field directs.
  3. 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"]
Threads hold messages and anchor to a passage, widget or page. Workflows track an update’s delivery and work stage; obligations track the answer owed and its next actor; to-dos keep a promise owed until explicitly ended. The same records supply each thread’s attention and the page-wide lists, including widget decisions.

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"]
Leaf captures valid changed source under 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

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.

Keep it private Publish the report

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

Before revision: queue policy selected, proposal text above it Queue policy · v1 Bursts can fill the queue. Choose how to handle them. ✓ Pause the producer Drop the oldest record
The user chooses a policy. Leaf records the selection against the widget’s stable id.
After revision: changed explanation, same selected queue policy Queue policy · v2 When full, pause upstream until space is available. ✓ Pause the producer Drop the oldest record
The agent revises the explanation. The new text appears; the user’s decision still applies. Schematic illustration.

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.

  1. observation Queued for Claude The comment appears in its thread immediately, reading Queued for Claude until the agent has it.
  2. 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.
  3. 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.
  4. 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.
  5. 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 leaf delivery ack. Claude Code reads a hook's output only once the hook exits, and Escape before then can drop it, so Leaf confirms the delivery once Claude Code's transcript shows it; if the turn ends without that, Leaf delivers the update again.

A leaf wait loop. The agent's leaf delivery ack, run before its next wait, confirmed the delivery the last wait printed.

Codex. The turn that the queued message opens began, or the agent confirmed a complete mid-turn reading with leaf delivery ack.

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's crossSessionInbound setting is accept.
  • 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"]
The first matching condition determines the base status. The table below shows its wording, including the agent’s name, and the activity detail supplied by each harness.

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> --ephemeral Names 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. --done delivers the result; --obligation asks the user back; --todo promises 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 … --obligation Opens 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 with leaf task end as 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 resolve A 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"]
Each input change produces one state. Renderers read it through selectors and repaint only when something they read changes; none reconstructs state from another renderer’s output.

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"]
Early scripts reserve element and panel geometry and provide a startup-failure notice, so late controls do not shift the page. Each element’s authored values are captured before its module changes the DOM.

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
The panel appears when JavaScript starts. Interface initialization and the state request overlap. New comments and drawings appear immediately and go through once saved comments and choices have loaded; choices that change saved state wait for the current reading. Saved threads and choices join the page without losing new comments or drafts. The panel then shows normal status; each comment keeps its own delivery status.

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"]
The local result stays visible until every renderer showing it has drawn the server’s version. The table below covers connection failures and refusals.
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"]
An unchanged token keeps the current reading. These are finite requests, with no long-lived connection.
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"]
Presentation dependencies set this order once. Code publishes a change; it does not schedule individual renderers.

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"]
Prose, styles and media can update through a patch, keeping unchanged nodes. Changed elements recapture authored values. A fresh document starts the new executable graph with its corresponding state. Both paths preserve the browser-owned state described below.
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.