Examples · Blog
A blog with comments on every story: 44 lines of AppSPEC
A journal with a featured carousel, topic filters, story pages rendered from Markdown, likes, responses on every story and related reading.
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, 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
3 screens, each a <STATE>. home, about 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="Stories" ICON="book-open">
…
<STATE ID="read" WIDTH="760px">
…
<STATE ID="about" NAV ICON="info" WIDTH="720px">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. It writes no WIRE at all: it uses the short forms OPEN. OPEN="state" opens an item in that state, and FORM SAVE or UPDATE saves the form to a collection; the engine adds the wires.
<CAROUSEL SRC="articles" SUBTITLE="excerpt" LIMIT="3" HEIGHT="340px" OPEN="read" />
…
BODY="excerpt" META="{category} · {minutes} min read" OPEN="read" />
…
<RELATED BY="category,tags" HEADING="Keep reading" META="{category} · {minutes} min read" LIMIT="3" OPEN="read" />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.
<TEXT STYLE="eyebrow" TEXT="{record.category}" />
<TEXT STYLE="display" TEXT="{record.title}" />
<TEXT STYLE="lead" TEXT="{record.excerpt}" />
…
<AVATAR NAME="{record.author}" />
<TEXT STYLE="small" TEXT="{record.author} · {record.date|date} · {record.minutes} min read" />04Social components attach themselves
COMMENT-THREAD, LIKES, RELATED bind to the record on screen through its GUID (for example articles/…). The spec never says which record: the engine supplies it, so the same tags work unchanged on a video, a product or a story.
<ROW><LIKES LABEL="found this useful" /></ROW>
…
<COMMENT-THREAD TITLE="Responses" PLACEHOLDER="Share a thought…" />
…
<RELATED BY="category,tags" HEADING="Keep reading" META="{category} · {minutes} min read" LIMIT="3" OPEN="read" />05One attribute sets the look
THEME="claude" sets every colour, radius and typeface. Change the word and the whole app restyles; all 31 themes work with every component.
<APP ID="blog" NAME="Field Notes" THEME="claude" LOGO="feather" WIDTH="1080px">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.
CAROUSEL ACTION→readOPEN="read" opens the clicked item in that stateCAROUSEL IMAGE→coverThe records have a cover fieldCAROUSEL TITLE→titleThe records have a title fieldCARDS ACTION→readOPEN="read" opens the clicked item in that stateCARDS TITLE→titleThe records have a title fieldCARDS SUBTITLE→authorThe records have a author fieldCARDS IMAGE→coverThe records have a cover fieldCARDS WHERE→category={filter-category}FILTER="category" adds filter pills and this condition- Filter pillsFILTER="category" adds these pills
IMG ALT→{record.title}The record's titleRELATED ACTION→readOPEN="read" opens the clicked item in that stateSTATE NAV→AboutNAV with no label uses the state ID, humanised
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 44-line document built from general-purpose components: ARTICLE, AVATAR, BUTTON, CARDS, CAROUSEL, CATEGORY-NAV, COMMENT-THREAD, DIVIDER, HERO, IMG, LIKES, RELATED, TEXT.
| Routing | Routes for the index, each story and the about page. |
|---|---|
| Content rendering | A safe Markdown renderer and typography for long-form text. |
| Comments | A comments model and API per story, and the form to post. |
| Discovery | A carousel, topic filters and a related-stories query. |
| Build tooling | A bundler or static-site generator and its configuration. |
The full specblog.appspec · 44 lines
<APP ID="blog" NAME="Field Notes" THEME="claude" LOGO="feather" WIDTH="1080px">
<STATE ID="home" NAV="Stories" ICON="book-open">
<HERO EYEBROW="The AppSPHERE journal" TITLE="Notes on building applications by describing them"
SUBTITLE="Short essays on declarative apps, the graph behind them and where the idea came from." />
<CAROUSEL SRC="articles" SUBTITLE="excerpt" LIMIT="3" HEIGHT="340px" OPEN="read" />
<TEXT STYLE="h2" TEXT="All stories" />
<CARDS SRC="articles" FILTER="category" SORT="date desc" COLS="3" RATIO="1200/630"
BODY="excerpt" META="{category} · {minutes} min read" OPEN="read" />
</STATE>
<STATE ID="read" WIDTH="760px">
<BUTTON STYLE="ghost" SIZE="sm" ICON="arrow-left" LABEL="All stories" ACTION="back" />
<COL GAP="12px">
<TEXT STYLE="eyebrow" TEXT="{record.category}" />
<TEXT STYLE="display" TEXT="{record.title}" />
<TEXT STYLE="lead" TEXT="{record.excerpt}" />
<ROW GAP="10px">
<AVATAR NAME="{record.author}" />
<TEXT STYLE="small" TEXT="{record.author} · {record.date|date} · {record.minutes} min read" />
</ROW>
</COL>
<IMG BIND="record.cover" RATIO="1200/630" RADIUS="16px" />
<ARTICLE BIND="record.body" />
<ROW><LIKES LABEL="found this useful" /></ROW>
<DIVIDER />
<COMMENT-THREAD TITLE="Responses" PLACEHOLDER="Share a thought…" />
<DIVIDER />
<RELATED BY="category,tags" HEADING="Keep reading" META="{category} · {minutes} min read" LIMIT="3" OPEN="read" />
</STATE>
<STATE ID="about" NAV ICON="info" WIDTH="720px">
<TEXT STYLE="h1" TEXT="About this journal" />
<ARTICLE>
Field Notes is a demo blog written entirely in AppSPEC. Every page you see is one of three states in a single file.
The comment thread under each story is the **same component** the video and store demos use. It is not told which story it belongs to; it binds to the record the page is showing.
- Stories come from a read-only snapshot
- Your likes and responses stay in your browser
</ARTICLE>
</STATE>
</APP><APP ID="blog" NAME="Field Notes" THEME="claude" LOGO="feather" WIDTH="1080px">
<STATE ID="home" NAV="Stories" ICON="book-open">
<HERO EYEBROW="The AppSPHERE journal" TITLE="Notes on building applications by describing them" SUBTITLE="Short essays on declarative apps, the graph behind them and where the idea came from." />
<CAROUSEL SRC="articles" SUBTITLE="excerpt" LIMIT="3" HEIGHT="340px" ACTION="read" IMAGE="cover" TITLE="title" />
<TEXT STYLE="h2" TEXT="All stories" />
<CATEGORY-NAV ID="filter-category" SRC="articles" FIELD="category" />
<CARDS SRC="articles" SORT="date desc" COLS="3" RATIO="1200/630" BODY="excerpt" META="{category} · {minutes} min read" WHERE="category={filter-category}" ACTION="read" TITLE="title" SUBTITLE="author" IMAGE="cover" />
</STATE>
<STATE ID="read" WIDTH="760px">
<BUTTON STYLE="ghost" SIZE="sm" ICON="arrow-left" LABEL="All stories" ACTION="back" />
<COL GAP="12px">
<TEXT STYLE="eyebrow" TEXT="{record.category}" />
<TEXT STYLE="display" TEXT="{record.title}" />
<TEXT STYLE="lead" TEXT="{record.excerpt}" />
<ROW GAP="10px">
<AVATAR NAME="{record.author}" />
<TEXT STYLE="small" TEXT="{record.author} · {record.date|date} · {record.minutes} min read" />
</ROW>
</COL>
<IMG BIND="record.cover" RATIO="1200/630" RADIUS="16px" ALT="{record.title}" />
<ARTICLE BIND="record.body" />
<ROW>
<LIKES LABEL="found this useful" />
</ROW>
<DIVIDER />
<COMMENT-THREAD TITLE="Responses" PLACEHOLDER="Share a thought…" />
<DIVIDER />
<RELATED BY="category,tags" HEADING="Keep reading" META="{category} · {minutes} min read" LIMIT="3" ACTION="read" />
</STATE>
<STATE ID="about" NAV="About" ICON="info" WIDTH="720px">
<TEXT STYLE="h1" TEXT="About this journal" />
<ARTICLE>Field Notes is a demo blog written entirely in AppSPEC. Every page you see is one of three states in a single file.
The comment thread under each story is the **same component** the video and store demos use. It is not told which story it belongs to; it binds to the record the page is showing.
- Stories come from a read-only snapshot
- Your likes and responses stay in your browser</ARTICLE>
</STATE>
</APP>