Examples · Paint studio
A desktop paint studio: 11 lines of AppSPEC
A creative paint workspace with brush and shape tools, fill, undo and redo, PNG export and a persistent gallery of kept sketches.
Running live in the sandbox with fixed demo data. Anything you add is saved in this browser; nothing leaves your device.
The state graph
2 screens, 0 wires
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
2 screens, each a <STATE>. studio, gallery 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="studio" NAV="Paint" ICON="brush">
…
<STATE ID="gallery" NAV="Gallery" ICON="images">02Screens connect by label
This app needs no <WIRE>: navigation comes from NAV labels, and its buttons call component actions directly, such as player.play({id}) or paint.keep(sketches).
<ROW><BUTTON LABEL="Keep current canvas" ACTION="paint.keep(sketches)" ICON="save" /><BUTTON LABEL="Return to canvas" ACTION="studio" STYLE="secondary" /></ROW>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}. The pipe drives filtering and display; no wire writes a record.
04One attribute sets the look
THEME="candy-magenta" sets every colour, radius and typeface. Change the word and the whole app restyles; all 31 themes work with every component.
<APP ID="chromatic-paint-studio" NAME="Chromatic" THEME="candy-magenta" LAYOUT="desktop">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.
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 11-line document built from general-purpose components: BUTTON, CANVAS, CARDS, COLLECTION.
| Canvas editing | Pointer strokes, shapes, fill and shared tool controls. |
|---|---|
| History | Undo, redo and a persistent local sketch gallery. |
| Export | PNG generation from the actual drawing surface. |
The full specchromatic-paint-studio.appspec · 11 lines
<?xml version="1.0" encoding="UTF-8"?>
<APP ID="chromatic-paint-studio" NAME="Chromatic" THEME="candy-magenta" LAYOUT="desktop">
<DATA MODE="local"><COLLECTION ID="sketches">[]</COLLECTION></DATA>
<STATE ID="studio" NAV="Paint" ICON="brush">
<CANVAS ID="paint" HEIGHT="100%" KEEP="sketches" TOOLS="brush,eraser,line,rect,ellipse,fill" PALETTE="#172033,#4056c7,#b5368a,#ef714e,#e3b341,#33765c,#ffffff" />
</STATE>
<STATE ID="gallery" NAV="Gallery" ICON="images">
<CARDS SRC="sketches" TITLE="title" SUBTITLE="author" IMAGE="image" COLS="4" EMPTY="Use Save PNG to export, or keep a sketch here with the action below." />
<ROW><BUTTON LABEL="Keep current canvas" ACTION="paint.keep(sketches)" ICON="save" /><BUTTON LABEL="Return to canvas" ACTION="studio" STYLE="secondary" /></ROW>
</STATE>
</APP><APP ID="chromatic-paint-studio" NAME="Chromatic" THEME="candy-magenta" LAYOUT="desktop">
<DATA MODE="local">
<COLLECTION ID="sketches">[]</COLLECTION>
</DATA>
<STATE ID="studio" NAV="Paint" ICON="brush">
<CANVAS ID="paint" HEIGHT="100%" KEEP="sketches" TOOLS="brush,eraser,line,rect,ellipse,fill" PALETTE="#172033,#4056c7,#b5368a,#ef714e,#e3b341,#33765c,#ffffff" />
</STATE>
<STATE ID="gallery" NAV="Gallery" ICON="images">
<CARDS SRC="sketches" TITLE="title" SUBTITLE="author" IMAGE="image" COLS="4" EMPTY="Use Save PNG to export, or keep a sketch here with the action below." />
<ROW>
<BUTTON LABEL="Keep current canvas" ACTION="paint.keep(sketches)" ICON="save" />
<BUTTON LABEL="Return to canvas" ACTION="studio" STYLE="secondary" />
</ROW>
</STATE>
</APP>