Pages
Writing a page
A page is an HTML document an agent writes around the work. This guide is for its author: what a page can hold, how it takes a theme, and which layout fits the work.
Using covers the person working on the page, and Extending covers building new widgets, themes and packages. How it works explains the mechanism behind all three.
In this topic
Parts of a page
The agent writes an HTML document containing the page's title, description, content, and layout. When the server delivers it, Leaf adds the page identity, visual theme, browser JavaScript, and canonical URL. The browser runtime supplies the commenting and collaboration interface. Authors build the content with these tools, starting with ordinary HTML:
-
Ordinary HTML. Leaf's reading theme, which a page takes
by default, styles headings, paragraphs, tables, code, and
detailswithout requiring CSS classes. These elements provide most of a page's content. -
Theme classes. The theme supplies classes for common
presentation patterns, including
.eyebrow,.lede,.callout,aside.notein alayout-noterow for a margin note, andol.stepsfor a numbered sequence like this list. -
Widgets. A widget is a complete page feature, authored
as a custom HTML element, such as
<lf-options>for choices and<lf-board>for cards in columns. Otherlf-*elements provide structure, such as<lf-pane>for a pane. The registry declares each element’s attributes and behavior using JSON Schema. The browser, validator, and agent instructions read those same declarations. -
Page-specific presentation. A local
<style>and inline SVG can define arrangements and drawings specific to the page. They use the theme's shared color and type tokens to stay consistent with the rest of Leaf. The Layout tab's drawings use this approach.
Leaf elements load their JavaScript modules when the page uses them.
Behavior specific to one page can use inline module scripts or local
modules under
page/. Reusable behavior belongs in a package, a directory
containing element declarations and their supporting files. The
page-behavior reference
defines module and dependency rules.
Widgets
These examples are interactive parts of this page. Their choices, card moves, and comments share its event log and Undo; as samples, they ask nothing, so On Me stays empty. Each activity feed shows the same page history, including actions in other examples.
Example page: a release review
Choose a rollout audience, then one follow-up job. Open Show activity to inspect both selections. Choose Undo in the page’s leaf menu to withdraw the job choice, then press Activity changed in the feed. The rollout audience remains selected.
Start with canary clients; expand after the error rate settles.
Which job should start first?
Recorded actions
Move “Flaky auth test” to “This week” by dragging its grip. With a keyboard, focus the grip, press Enter to grab, Right to move, and Enter to drop. On a narrow touch screen, use the card’s Move to buttons. Open Show activity to inspect the recorded move. Choose Undo in the page’s leaf menu to return the card to Incoming; press Activity changed in the feed to see the move marked undone.
Recorded actions
The widget catalog also includes diagrams, charts, diffs with collapsible files, annotated code, editable drafts, playgrounds, and recorded browser traces. The examples gallery opens complete pages using them.
Asking the user
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.
Each ask joins the user's On Me list until the widget or thread that asks it is answered (On Me and the agent’s list).
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.
In this topic
Styles and overrides
The user chooses the visual direction. A theme supplies shared colors, type and reusable styles; widget packages draw their own controls; the author arranges the page and makes local adjustments.
flowchart TD accTitle: A page captures its selected theme accDescr: The author selects Editorial from the user's themes. Leaf captures that theme in the release-review page, and the export carries the same theme. Changing the user's default later changes neither existing copy. Themes["User's themes<br/>Editorial · Dense"] -->|select Editorial| Page["Release review<br/>Captured: Editorial"] Page -->|export| Export["Exported review<br/>Same Editorial theme"]
leaf theme list shows the user’s named themes. Changing the
default later leaves this page and its export intact.
Choosing a theme and honoring browser preferences
leaf theme list exposes the user's named themes and their
author instructions. page init --theme NAME selects one; a
new page otherwise uses the user's default, or Leaf's reading theme. A
page with a design wholly its own takes none with
--no-theme and starts from the browser's defaults, while
Leaf's widgets, threads and panel keep their faces. Re-initializing
keeps the captured choice unless explicitly changed. The author follows
the user's request first, then the task and the theme's instructions.
The reading theme supports light and dark schemes. Authors preserve
supported platform preferences when composing a page; an explicit visual
comparison may need a fixed appearance, declared with
color-scheme. CSS precedence determines which rule wins,
not who has authority over the work.
Make a local adjustment: colors, width or controls
| To change | Write |
|---|---|
| Leaf's colors | :root { --accent: teal; } |
| A Layout's width | :root { --col: 46rem; } |
| A widget | lf-card { padding: 8px; } |
| Controls a widget builds |
lf-board .lf-grip { color: teal; }
|
A Layout reads tokens such as --col,
--sidebar and --page-pad-start, and any other
rule changes it too, such as main { padding-top: 28px; }.
A bare button or input rule styles every one
in the document, the controls a widget builds included, so name the
widget to aim a rule at its controls. Leaf’s panel and thread cards keep
their own look against a rule as light as one class, and inherit theme
inputs such as --accent and --sans from
:root.
For stylesheet roles, cascade precedence and nested widget boundaries, see the CSS contract in Extending.
In this topic
Layouts
Leaf supplies page layouts and a layout for blocks within a page. A column, sidebar, or workspace sets the page’s shape; tiles arrange a block’s children and can be used within any of them. The table and drawings below show the same classes in the same order.
| Class | Arrangement |
|---|---|
layout-column
Page layout
|
Text in a centred 720px column; figures, boards and tables may extend past it. Suits plans, reviews and explanations, including this page. |
layout-sidebar
Page layout
|
A body beside a narrower side column, with the header across both, as wide as the window up to 1600px. A board and its status, a release dashboard, or a review with its verdict beside the diff. |
layout-workspace
Page layout
|
Task regions kept together: a queue beside its selected item, or controls beside a preview. In a large window, the workspace fills the height and its panes scroll separately. |
layout-tiles
Block layout
|
Equal cells, as many to a row as fit. A row of metrics or status tiles. |
layout-column on main. Text keeps the 720px
column, and a block with a declared width, such as a diagram, extends
past it.
layout-sidebar on main widens the page and
sets a body beside its side column. Every block starts at its region's
left edge, and text retains its reading width.
layout-workspace keeps task regions together. In a large
window it fills the height, and its panes scroll separately.
layout-tiles arranges children in equal cells, wrapping
as the available width changes. The surrounding page keeps its own
layout.
Leaf's Layout classes are ordinary CSS, applied beneath the page's own stylesheet in the cascade. A page can adjust them with CSS rules, for example to change a column's share of the width or a gap. It can also use its own grid when the supplied layouts do not fit.
The reading column
On a layout-column page, headings, paragraphs, lists, and
code occupy a centred column up to 720px wide. On a sidebar page too, the
theme limits paragraphs, list items, and headings to that reading width. A
workspace applies neither the reading width nor the limits below: every
block fills its pane.
A diagram, board, or wide table can extend into the margins beyond the reading column. The text above and below it keeps its width and position. These width settings control the block:
-
columnis the text measure. In a wider region, such as a sidebar page's body, acolumnblock starts at the same left edge as the text. -
widestarts at the edge the text starts from (the left, in a left-to-right page) and extends toward its end, up to about 910px, enough for 100 columns of code. Wide blocks share this width so they align with one another. -
availableuses the window's width after subtracting the outer page margins and space for margin notes and controls.
The registry, which declares each element's
behavior, also gives it a default width. For example,
lf-options uses column,
lf-diff uses wide, and
lf-board uses available. A
data-width attribute asks for wide or
available on any block, overriding the default. If the
margins have unequal space, an available block extends
farther into the wider margin. A table given one of these widths uses its
columns' natural width, up to that limit, rather than stretching across
the extra space.
data-width on a block The block extends
past the column, and the paragraphs around it keep their width.
A log, feed, or long listing with data-bound scrolls within a
limited height and opens at its newest entry. Its growing content
therefore does not make the whole page longer.
Margins
A sufficiently wide window places these controls and notes beside the reading column:
- The rail, on the right, where Leaf places a marker beside each commented or decided passage. It needs a window about 960px wide, or about 1010px on a touch screen.
-
Sidenotes, each an
aside.notepaired with its passage in alayout-noterow, on the right when the row has enough room.
The browser places these compositions as soon as the HTML arrives. In a narrow window, the sidebar stacks in source order and each note follows its passage. Notes never shift the reading column.
Workspace
layout-workspace fits the page to the window above Leaf's
panel. A single-row header holds the title and status tags.
The body takes the remaining height and contains an lf-pane,
a widget that arranges its own panes, or a grid of panes defined by the
page's CSS. Each pane scrolls its content beneath its own
header. A pane's footer stays visible, keeping its actions
within reach.
An lf-tabs list="side" in the workspace body displays a list
of tabs as a queue, such as tickets or findings. The selected item's
content appears beside that list.
The workspace fills the window's height when the window is at least 720px wide and 480px tall. In a smaller window, panes expand to fit their content and the whole page scrolls.
<main class="layout-workspace density-working">
<header><h1>Payments alerts</h1></header>
<div id="regions">
<lf-pane label="Queue">…</lf-pane>
<lf-pane label="Alert">…</lf-pane>
</div>
</main>
<style>
#regions { display: grid; grid-template-columns: 1fr 2fr; }
@media (width < 720px) { #regions { grid-template-columns: 1fr; } }
</style>
density-working reduces type size and spacing for a compact
working surface. It is independent of the Layout: a workspace can use
ordinary reading sizes, and a pane or table on another page can use
working density.
Examples: Notification playground, Merge a worktree, Alert review.
Tiles
layout-tiles arranges an element's children in equal cells at
least 9rem wide, placing as many cells in each row as fit. Cells in the
same row have equal heights. A page can set its own columns when it needs
a particular grouping, such as four tiles in two rows of two. A row of
headline numbers can use a layout-tiles block of native
dl elements with labels in dt and values in
dd.
Tabs and parts
An lf-tabs at the top of a page creates tabs like the ones on
this page. Their tab strip stays at the window's top while the page
scrolls, and one tab's content is visible at a time. All tabs share the
page's width, Layout, threads, and version history.
When different parts of the work need different layouts,
lf-parts holds an lf-part for each. Each part
contains its own layout container; main itself carries no
Layout. For example, one part can hold an interactive workspace and
another a proposal read in a column. Parts share the page’s history,
threads, and On Me list. The
page-layout reference
gives the markup.
Narrow windows
In a narrow window, page regions stack vertically in HTML order. Margin notes move into the reading column, a side column appears above or below its body, and workspace panes expand to fit their content. Tiles can still fit two per row on a 390px phone.