Views and actions
A schema describes what a kind of object is. Two more things can be attached to it: how its objects are drawn, and what they can do.
Together they turn a schema from a storage format into something closer to a small application. A character has a character sheet and a "write a voice line" button; a recipe has a recipe page and a "shopping list" button. Neither needed any code.
Both are properties of the type, not of one object. You decide once, on the schema, and every object of that schema has it.
Views
A view is a template: markup over a fixed vocabulary of components, with
{{field}} bindings that fill in from the object being shown.
Views live on the Views tab of the schema editor, and each is a file in the project, so they diff in git and travel with the folder like everything else.
A schema with no view still opens — the form generated from the schema is the fallback. A view is an override, and it is allowed to show fewer fields than the schema has. A character sheet that puts the secret on its own tab and leaves three bookkeeping fields out entirely is doing its job.
Boxes
A view is written for a box — the kind of place it will be inserted into:
| Box | Where it goes |
|---|---|
page |
A full page: the Page tab of the object, and a dock tab of its own. |
card |
A compact card in a list. |
panel |
A side panel. |
The list of boxes is fixed. A box is a contract about how much room there is and what surrounds it, so boxes are something Zero provides and a template chooses — a box nobody knows how to draw would just be a rectangle.
One view per schema per box. Once every box is taken, the editor stops offering New view.
Today the
pagebox is the one that renders. Views written forcardandpanelare stored and checked, but nothing displays them yet.
Writing a template
The syntax is HTML-shaped, and the vocabulary is Zero's own. There is no div
and no class: layout is made of named primitives, so the set is finite, the
result is predictable, and a template cannot break the surrounding page.
Layout — stack, row, grid, split, card, divider, spacer,
scroll.
Content — field, image, list, table, link, button, tabs/tab,
if, note.
Text — h1, h2, h3, p, text, b, i.
A small example:
<row gap="lg">
<image of="portrait" size="lg" />
<stack gap="sm">
<h1>{{name}}</h1>
<text muted="true">{{role}}</text>
<if when="status" is="Missing">
<note kind="warn">Missing. The table has not been told yet.</note>
</if>
<button action="voice-line">Voice line</button>
</stack>
</row>
<table of="ties">
<item>
<b>{{who}}</b>
<text>{{note}}</text>
</item>
</table>
A few rules worth knowing before you start:
- The template must be well-formed XML. Every tag closed, every attribute
quoted — the same rule JSX has. That is not pedantry: without a real tree there
is no way to know that
{{title}}inside a list refers to the list's entries rather than to the object. {{name}}and{{guid}}are reserved and mean the object's own name and id, which are not inside its fields.<image of="…">names a field, not a file. The field's value is the path —photos/ribollita.png. Pointingofstraight at a path resolves to nothing.<list>and<table>need an<item>describing one entry. Inside it, paths are relative to the entry, and{{.}}is the entry itself when the list holds plain values. In atable, each child of<item>becomes a column.- A reference can be followed one step: if
homepoints at a location,{{home.name}}and{{home.guid}}work.{{home.region.x}}does not. <link to="{{home.guid}}">opens the object it names, as its own page.
The editor has a check that resolves every path and action against the schema as you type, which is where a typo shows up.
What is checked and what is not
Zero checks references: that a field path exists, that an action exists, that a list is a list. It deliberately does not check tag names. The vocabulary belongs to the part of Zero that draws the page, so an unknown tag renders as a small flagged box rather than being rejected — which is also how new components can appear without anything else having to change.
Actions
An action is a button on every object of the schema. Pressing it runs a workflow with that object as its single input row.
It is deliberately a binding to an existing workflow, not a scripting language.
A workflow is already a function whose signature Zero works out for itself, and
its input is rows of {variable: value} — an object is exactly one such row, as
soon as something says which field feeds which variable. That something is the
action's bind.
Actions live on the Actions tab of the schema editor. Each has:
- a name, used from a view:
<button action="voice-line">; - a label for the button;
- the workflow it runs;
- the bind — one entry per variable the workflow needs.
A bind points at a field path (appearance, traits.might) or at one of the two
reserved tokens $name and $guid, which address the object's identity
rather than its data.
The binding is checked when the schema is saved, not when the button is pressed: a renamed field or a deleted workflow surfaces in the editor, while you are looking at it.
Running one
Pressing an action starts an ordinary background job. It appears in Jobs, it counts against the same spending budget as any other run, and it can be stopped from the button itself.
Two things are worth knowing:
- A field the action binds must have a value. If it does not, the run is refused with a message naming the field. Producing something from a blank is worse than saying the blank is there.
- Buttons are disabled while the object has unsaved changes, and say so. The run reads the object as it is saved on disk, so acting on an unsaved edit would quietly use the old values.
An action produces — a file, an asset, a chat. Writing a result back into a field of the object is not possible yet.
Not yet
Three things are deliberately absent rather than forgotten:
- Cross-object listing. A view draws one object. A list bound to a field of that object works; "every quest in the project" is not something a template can ask for — that is what navigator sections are for.
- Plugins. A second floor, where real code runs in a sandbox and can register new components, is designed but not built.
- Writing back. Actions produce; they do not yet edit the object they ran on.