Examples · Paint
A paint app with shapes and undo: 43 lines of AppSPEC
A paint app with brush, eraser, line, rectangle, ellipse and fill tools, a palette, undo and redo, PNG export, and a gallery of kept sketches with likes.
Running live in the sandbox with fixed demo data. Anything you add is saved in this browser; nothing leaves your device.
The state graph
3 screens, 1 wire
Screens are nodes and WIRE labels are edges. Screens in blue appear in the navigation; orange edges write a record on the way.
How it works
Walking through the spec
01Screens are states
3 screens, each a <STATE>. draw, kept appear in the navigation because they carry a NAV label. Children lay themselves out by containment: ROW, COL and GRID arrange them, and everything else stacks.
<STATE ID="draw" NAV ICON="brush">
…
<STATE ID="kept" NAV ICON="images">
…
<STATE ID="sketch" WIDTH="920px">02Screens connect by label
Each <WIRE> names an action and where it leads; a button, card or row that fires the label follows it, taking the clicked record along. This app writes 1 wire out and uses the short forms OPEN: OPEN="state" opens an item in that state, and FORM SAVE or UPDATE saves the form to a collection, with the engine adding the wire.
<CARDS SRC="sketches" SORT="created desc" COLS="3" RATIO="16/10" META="{author} · {created|ago}" OPEN="sketch"
…
<WIRE LABEL="discard" DELETE="sketches" TO="kept" />03Data flows through the key-value pipe
Every input publishes its value into the pipe under its ID, and every screen can read the pipe with {placeholders}. Writes are declared on wires: discard delete sketches. SET adds key-value pairs to the record being written.
<INPUT ID="title" PLACEHOLDER="Name this sketch" KEEP="true" WIDTH="220px" />
…
<TEXT STYLE="h1" TEXT="{record.title}" />04Social components attach themselves
LIKES bind to the record on screen through its GUID (for example sketches/…). The spec never says which record: the engine supplies it, so the same tags work unchanged on a video, a product or a story.
<LIKES />05One attribute sets the look
THEME="figma" sets every colour, radius and typeface. Change the word and the whole app restyles; all 31 themes work with every component.
<APP ID="paint" NAME="Sketchbook" THEME="figma" LOGO="palette" WIDTH="1240px">Convention over configuration
What the engine infers
Every attribute this spec leaves out has a default inferred from names, data and context. These are the ones this app relies on; writing any of them out would override it.
STATE NAV→Draw,KeptNAV with no label uses the state ID, humanisedCARDS ACTION→sketchOPEN="sketch" opens the clicked item in that state
What this would typically take
The pieces a conventional stack needs
A typical implementation of the same app assembles these parts, each with its own code, libraries and configuration. The size depends on the team and the tools, so we do not quote a number for it.
Here the same app is one 43-line document built from general-purpose components: BUTTON, BUTTON-BAR, CANVAS, CARDS, IMG, INPUT, LIKES, TEXT.
| Canvas engine | Pointer handling, stroke rendering, shape previews and a flood fill. |
|---|---|
| History | Undo and redo stacks. |
| Toolbar | Tool, colour and size controls wired to the canvas. |
| Persistence | Saving drawings and listing them in a gallery. |
| Build tooling | A bundler, a dev server and a build configuration. |
The full specpaint.appspec · 43 lines
<APP ID="paint" NAME="Sketchbook" THEME="figma" LOGO="palette" WIDTH="1240px">
<STATE ID="draw" NAV ICON="brush">
<ROW JUSTIFY="between">
<COL GAP="2px">
<TEXT STYLE="h1" TEXT="Sketchbook" />
<TEXT STYLE="muted" TEXT="Brush, shapes and fill. Your drawing is kept in this browser as you go." />
</COL>
<ROW GAP="8px">
<INPUT ID="title" PLACEHOLDER="Name this sketch" KEEP="true" WIDTH="220px" />
<BUTTON STYLE="secondary" ICON="file-plus" LABEL="New" ACTION="paint.new()" />
<BUTTON ICON="bookmark-plus" LABEL="Keep" ACTION="paint.keep(sketches)" />
</ROW>
</ROW>
<CANVAS ID="paint" HEIGHT="560px" BACKGROUND="#ffffff" />
<BUTTON-BAR JUSTIFY="start">
<BUTTON LABEL="Pencil" ICON="pencil" ACTION="paint.size(3)" />
<BUTTON LABEL="Marker" ICON="highlighter" ACTION="paint.size(18)" />
<BUTTON LABEL="Ink" ICON="droplet" ACTION="paint.color(#1d3cc6)" />
<BUTTON LABEL="Coral" ICON="droplet" ACTION="paint.color(#f97362)" />
<BUTTON LABEL="Clear" ICON="trash-2" ACTION="paint.clear()" />
</BUTTON-BAR>
</STATE>
<STATE ID="kept" NAV ICON="images">
<TEXT STYLE="h1" TEXT="Kept sketches" />
<CARDS SRC="sketches" SORT="created desc" COLS="3" RATIO="16/10" META="{author} · {created|ago}" OPEN="sketch"
EMPTY="Nothing kept yet. Draw something and press Keep." />
</STATE>
<STATE ID="sketch" WIDTH="920px">
<BUTTON STYLE="ghost" SIZE="sm" ICON="arrow-left" LABEL="Kept sketches" ACTION="back" />
<TEXT STYLE="h1" TEXT="{record.title}" />
<IMG BIND="record.image" FIT="contain" RADIUS="16px" />
<ROW>
<LIKES />
<BUTTON STYLE="danger" ICON="trash-2" LABEL="Delete" ACTION="discard" />
</ROW>
</STATE>
<WIRE LABEL="discard" DELETE="sketches" TO="kept" />
</APP><APP ID="paint" NAME="Sketchbook" THEME="figma" LOGO="palette" WIDTH="1240px">
<STATE ID="draw" NAV="Draw" ICON="brush">
<ROW JUSTIFY="between">
<COL GAP="2px">
<TEXT STYLE="h1" TEXT="Sketchbook" />
<TEXT STYLE="muted" TEXT="Brush, shapes and fill. Your drawing is kept in this browser as you go." />
</COL>
<ROW GAP="8px">
<INPUT ID="title" PLACEHOLDER="Name this sketch" KEEP="true" WIDTH="220px" />
<BUTTON STYLE="secondary" ICON="file-plus" LABEL="New" ACTION="paint.new()" />
<BUTTON ICON="bookmark-plus" LABEL="Keep" ACTION="paint.keep(sketches)" />
</ROW>
</ROW>
<CANVAS ID="paint" HEIGHT="560px" BACKGROUND="#ffffff" />
<BUTTON-BAR JUSTIFY="start">
<BUTTON LABEL="Pencil" ICON="pencil" ACTION="paint.size(3)" />
<BUTTON LABEL="Marker" ICON="highlighter" ACTION="paint.size(18)" />
<BUTTON LABEL="Ink" ICON="droplet" ACTION="paint.color(#1d3cc6)" />
<BUTTON LABEL="Coral" ICON="droplet" ACTION="paint.color(#f97362)" />
<BUTTON LABEL="Clear" ICON="trash-2" ACTION="paint.clear()" />
</BUTTON-BAR>
</STATE>
<STATE ID="kept" NAV="Kept" ICON="images">
<TEXT STYLE="h1" TEXT="Kept sketches" />
<CARDS SRC="sketches" SORT="created desc" COLS="3" RATIO="16/10" META="{author} · {created|ago}" EMPTY="Nothing kept yet. Draw something and press Keep." ACTION="sketch" />
</STATE>
<STATE ID="sketch" WIDTH="920px">
<BUTTON STYLE="ghost" SIZE="sm" ICON="arrow-left" LABEL="Kept sketches" ACTION="back" />
<TEXT STYLE="h1" TEXT="{record.title}" />
<IMG BIND="record.image" FIT="contain" RADIUS="16px" />
<ROW>
<LIKES />
<BUTTON STYLE="danger" ICON="trash-2" LABEL="Delete" ACTION="discard" />
</ROW>
</STATE>
<WIRE LABEL="discard" DELETE="sketches" TO="kept" />
</APP>