Examples · Video site
A YouTube-style video site: 58 lines of AppSPEC
A video site built on Blender open movies: a home grid with category filters, a watch page with the player, likes, saves, comments and an up-next list, full-text search and a shorts playlist.
Running live in the sandbox with fixed demo data. Anything you add stays in this browser.
The state graph
4 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
4 screens, each a <STATE>. home, search, shorts 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="home" NAV="Home" ICON="house">
…
<STATE ID="watch">
…
<STATE ID="search" NAV="Search" ICON="search">
…
<STATE ID="shorts" NAV="Shorts" ICON="zap">02Screens connect by label
Each <WIRE> names an action and where it leads. A button, card or row that fires the label follows the wire; the clicked record travels with it. This app declares 1 wire.
<WIRE LABEL="watch" TO="watch" />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}. No wire writes a record here; the pipe drives filtering and display, and comments and likes are stored by the social components themselves.
<CATEGORY-NAV ID="cat" SRC="videos" FIELD="category" />
…
<VIDEO YOUTUBE="{record.youtube}" TITLE="{record.title}" />
<TEXT STYLE="h2" TEXT="{record.title}" />
…
<AVATAR NAME="{record.channel}" />
…
<TEXT STYLE="h3" TEXT="{record.channel}" />
<TEXT STYLE="small" TEXT="{record.views|num} views · {record.year}" />
…
<ROW GAP="6px"><BADGE TEXT="{record.category}" /><BADGE TONE="muted" TEXT="{record.licence}" /></ROW>
<TEXT TEXT="{record.description}" />
…
<INPUT ID="q" TYPE="search" PLACEHOLDER="Search films…" LIVE="true" />04Social components attach themselves
ACTION-BAR, COMMENT-THREAD, RELATED bind to the record on screen through its GUID (for example videos/…). The spec never says which record: the engine supplies it, so the same tags work unchanged on a video, a product or a story.
<ACTION-BAR />
…
<COMMENT-THREAD />
…
<RELATED BY="tags,category" HEADING="Up next" META="{channel} · {duration|dur}" LIMIT="8" ACTION="watch" />05One attribute sets the look
THEME="linear" sets every colour, radius and typeface. Change the word and the whole app restyles; all 31 themes work with every component.
<APP ID="clips" NAME="Clips" THEME="linear" LOGO="clapperboard" WIDTH="1240px">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 58-line document built from general-purpose components: ACTION-BAR, AVATAR, BADGE, CARD, CATEGORY-NAV, COMMENT-THREAD, INPUT, LIST, PLAYLIST, RELATED, TEXT, VIDEO.
| Routing | Routes and navigation for home, watch, search and shorts, plus passing the current video between them. |
|---|---|
| Video player integration | Embedding and sizing a third-party player, with a thumbnail placeholder while it loads. |
| Comments | A comments data model, an API to read and post them per video, threading and a form component. |
| Likes and saves | Per-video counters, per-user state and the endpoints behind them. |
| Related videos | A query that ranks other videos by shared tags and category. |
| Search and filters | Search input handling, filtering, empty states and category pills. |
| State and styling | State management across screens, a component library and styles for every screen. |
| Build tooling | A bundler, a dev server and a build configuration. |
The full specclips.appspec · 58 lines
<APP ID="clips" NAME="Clips" THEME="linear" LOGO="clapperboard" WIDTH="1240px">
<DATA SRC="api/" />
<STATE ID="home" NAV="Home" ICON="house">
<ROW JUSTIFY="between" ALIGN="end">
<COL GAP="2px">
<TEXT STYLE="eyebrow" TEXT="Open movies" />
<TEXT STYLE="h1" TEXT="Watch something made in the open" />
</COL>
<TEXT STYLE="small" TEXT="Films by Blender Studio, CC BY" />
</ROW>
<CATEGORY-NAV ID="cat" SRC="videos" FIELD="category" />
<LIST SRC="videos" WHERE="category={cat}" COLS="4" THUMB="thumb" TITLE="title"
SUBTITLE="{channel} · {year}" META="{views|compact} views" BADGE="{duration|dur}" ACTION="watch" />
</STATE>
<STATE ID="watch">
<ROW ALIGN="start" GAP="28px">
<COL SPAN="2" MIN="320px" GAP="16px">
<VIDEO YOUTUBE="{record.youtube}" TITLE="{record.title}" />
<TEXT STYLE="h2" TEXT="{record.title}" />
<ROW JUSTIFY="between">
<ROW GAP="10px">
<AVATAR NAME="{record.channel}" />
<COL GAP="0">
<TEXT STYLE="h3" TEXT="{record.channel}" />
<TEXT STYLE="small" TEXT="{record.views|num} views · {record.year}" />
</COL>
</ROW>
<ACTION-BAR />
</ROW>
<CARD STYLE="flat" GAP="6px">
<ROW GAP="6px"><BADGE TEXT="{record.category}" /><BADGE TONE="muted" TEXT="{record.licence}" /></ROW>
<TEXT TEXT="{record.description}" />
</CARD>
<COMMENT-THREAD />
</COL>
<COL MIN="280px">
<RELATED BY="tags,category" HEADING="Up next" META="{channel} · {duration|dur}" LIMIT="8" ACTION="watch" />
</COL>
</ROW>
</STATE>
<STATE ID="search" NAV="Search" ICON="search">
<INPUT ID="q" TYPE="search" PLACEHOLDER="Search films…" LIVE="true" />
<LIST SRC="videos" WHERE="title LIKE {q}" LAYOUT="rows" THUMB="thumb" TITLE="title"
SUBTITLE="{channel} · {year}" META="{description}" BADGE="{duration|dur}" ACTION="watch" EMPTY="Nothing matches." />
</STATE>
<STATE ID="shorts" NAV="Shorts" ICON="zap">
<TEXT STYLE="h2" TEXT="Under five minutes" />
<PLAYLIST SRC="videos" WHERE="duration<300" SORT="duration" META="{channel} · {duration|dur}" ACTION="watch" />
</STATE>
<WIRE LABEL="watch" TO="watch" />
</APP>