Screen specs
Turn a flow's nodes into screens that can actually be built. Each screen is
documented twice from one source — a mock showing what it should look like, and
the spec a developer implements from — so the picture and the contract cannot
disagree.
The two rules
- Every screen traces to a flow node and an acceptance criterion. No orphan
screens. If a criterion cannot become a screen or a state, that is a finding
about the criterion — record it in
notes, don't invent a screen for it.
- Real content only. A mock reading "Hotel Name / $XXX" teaches nobody
anything. Use values that exist in the project's seed data.
Workflow
Settle the design system first, once, in design-system.json: colour
roles (light and dark), the M3 type scale mapped to real platform text
styles, shape, elevation, spacing, a component inventory covering every
region type, and the standing platform calls — the conflicts decided
app-wide so no screen re-litigates them. Choose the target (mobile or
web), the style for web (material / neo-flat / minimal-neutral /
fluent), and the device for mobile (iphone / iphone-max /
android / iphone-se) here too.
Author one flow's specs as the pattern, then fan the rest out against it.
Getting the system right on the hardest flow is what makes parallel work
safe.
Build.
node scripts/build-screens.mjs \
--system <design-system.json> --specs <dir> --out <dir> [--img ../assets/img/]
Every flow page carries a journey filmstrip above its screens — the
flow's screens ordered by their node id (whole numbers ascending, then
decimals under their parent; node-less screens sort last), each a small
clickable card that jumps to its full section below. It renders in both
layouts.
Add --layout story to build into a shared design-story site instead
of a standalone set: pages land in <out>/screens/<slug>.html (one
directory deeper — a relative --img base gets an extra ../
automatically) so they sit next to user-flow-maps' flows/ output. Under
story, each screen's Flow node value links back to
../flows/<flowSlug>.html#node-<id>, and its Criteria chips link to
../coverage.html#ac-<id> — both resolve once the sibling builds
(build-flowmaps.mjs --layout story and build-story.mjs) have run;
until then they just 404, no build depends on the other running first.
Verify. See references/verifying.md. Check that no mock overflows its
frame, no image is broken, and the page never scrolls sideways.
Tie flows and screens into one site. Once both builds have written
into the same <out> with --layout story, run
node scripts/build-story.mjs --flows <flowspec dir|file> --screens <dir> --out <dir>
to generate <out>/index.html (the design-story hub) and
<out>/coverage.html (the acceptance-criteria coverage matrix). See
"Design-story site" below for the full recipe.
What a spec carries
Per screen: purpose, flow node, criteria, presentation kind, ordered regions
(each with its Material component and exact token names), every state with its
trigger and what changes, the platform calls, seeded content, accessibility
notes, implementation hints, grounding references, and anything left open.
Full contract: references/schema.md.
Presentation drives the chrome
How nav.kind and the surrounding chrome render depends on the target:
- mobile — device frame, tab bars, nav bars, sheets, and the
MD3-vs-platform calls. See
references/targets/mobile.md for the full
nav.kind table and the device library.
- web — responsive breakpoints (mobile-web / tablet / desktop), the app
shell (top bar, side nav, dialogs), and the four selectable styles
(Material UI, Neo-Flat, Minimal-Neutral, Fluent). See
references/targets/web.md.
Describe nav affordances plainly — "share, favorite" — and they render as
icons. Prose asides in parentheses are stripped, because a mock that prints
"(SF Symbols, not text buttons)" in its nav bar is documenting itself instead of
showing the screen.
Region types
appbar searchfield chips segmented hero gallery list card row
field datefield stepper price banner notice error cta
secondary-cta divider text footnote empty skeleton sheet-handle
map
Each renders in the mock and maps to a component in the inventory. Adding a type
means adding a renderer in screenspec.js and an inventory row — a type with
no inventory entry is a spec that can't be built.
Conventions that keep specs honest
- Name tokens exactly.
surfaceContainerHigh, not "light grey". A developer
maps tokens to a palette; adjectives map to nothing.
- State the platform call wherever the design system and the platform
disagree, with which won and why. An unstated blend is what produces visible
seams.
- Design the ugly states. Empty, error, loading, disabled, and the
domain-specific ones the criteria demand. A spec with only a happy path will
be built with only a happy path.
- A state that renders nothing is still a state. If the correct behaviour is
"discard silently, change nothing on screen", write that down — otherwise
someone will add a spinner.
- Mark known gaps as gaps. A control whose destination doesn't exist gets
specced as the gap it is, never filled with an invented screen.
- Structure, not pixels. No coordinates, no px widths. Say what is present
and in what order; the renderer lays it out.
Styling
Everything resolves through design-system.json's tokens — colour roles become
CSS custom properties, the shape scale becomes radii. Swap the palette there and
every mock in the set follows. Light and dark are both generated.
Design-story site
The end-to-end recipe that turns the two skills' separate outputs into one
navigable site a reviewer walks top to bottom — Problem → Journey → Screens →
Coverage, everything cross-linked:
node <user-flow-maps>/scripts/build-flowmaps.mjs <flowspec.json> \
--out <dir> --layout story --screens <screens dir>
node scripts/build-screens.mjs \
--system <design-system.json> --specs <screens dir> --out <dir> --layout story
node scripts/build-story.mjs \
--flows <flowspec.json|dir> --screens <screens dir> --out <dir> [--system <design-system.json>]
All three point --out at the same directory. Order doesn't matter for
correctness (each build degrades to a dead link, never a failure, if a
sibling hasn't run yet), but running build-story.mjs last means its hub and
coverage page reflect what's actually on disk. The result:
<dir>/
index.html # the hub: Problem, Journey, Screens, Coverage
coverage.html # every AC id x which node(s)/screen(s)/state(s) realize it
flows/<slug>.html
screens/<slug>.html
Open <dir>/index.html. It links to each flows/<slug>.html (the journey),
groups screen links by flow in journey order, and summarizes coverage with a
link to the full matrix — it never re-renders a mock or a poster, only links
to the pages the other two builds produced.
Files
| Path |
What it is |
scripts/screenspec.js |
Mobile renderer: tokens, region renderers, device mock, state resolution. Browser and Node. |
scripts/screenspec.web.js |
Web renderer: responsive breakpoints, app shell, region renderers for the web target. Browser and Node. |
scripts/styles.js |
The four selectable web styles (Material UI, Neo-Flat, Minimal-Neutral, Fluent) — palette and token overrides per style. |
scripts/build-screens.mjs |
CLI: design system + specs → linked HTML pages, one per flow (with a journey filmstrip), plus a design-system index. --layout story cross-links into a shared design-story site. |
scripts/journey.mjs |
journeyOrder(screens) — sorts a flow's screens into journey order by node id; backs the filmstrip and the hub's per-flow screen groups. |
scripts/build-story.mjs |
CLI: flow spec(s) + screen spec(s) → index.html (design-story hub) and coverage.html (AC coverage matrix). Reads both builds' inputs, renders neither's mocks — only links. |
references/schema.md |
The spec contract, field by field. |
references/verifying.md |
What to check before calling the set done. |
references/coverage-both-ways.md |
Node→screen and screen→node coverage. Run after any flow-map edit — a retired node leaves an orphaned screen that a one-directional check cannot see. |
1---2name: screen-specs3description: Use when screens need designing or documenting as reference a developer or agent can build from — "design the screens", "screen specs", "what should this screen look like", "turn the flows into designs", "document the UI", or when a flow map's nodes need to become actual screens. Produces standalone HTML pages showing each screen as a device-framed mock beside its spec — regions with Material component and token names, every state the criteria demand, the MD3-vs-platform calls, accessibility notes, and traceability back to a flow node and acceptance criterion. Mock and spec are generated from one source so they cannot drift. NOT for flow/journey diagrams (use user-flow-maps), NOT a design tool replacement, and NOT for writing the screens' production code.4---56# Screen specs78Turn a flow's nodes into screens that can actually be built. Each screen is9documented twice from one source — a mock showing what it should look like, and10the spec a developer implements from — so the picture and the contract cannot11disagree.1213## The two rules14151. **Every screen traces to a flow node and an acceptance criterion.** No orphan16 screens. If a criterion cannot become a screen or a state, that is a finding17 about the criterion — record it in `notes`, don't invent a screen for it.182. **Real content only.** A mock reading "Hotel Name / $XXX" teaches nobody19 anything. Use values that exist in the project's seed data.2021## Workflow22231. **Settle the design system first**, once, in `design-system.json`: colour24 roles (light and dark), the M3 type scale mapped to real platform text25 styles, shape, elevation, spacing, a component inventory covering every26 region type, and the **standing platform calls** — the conflicts decided27 app-wide so no screen re-litigates them. Choose the `target` (`mobile` or28 `web`), the `style` for web (`material` / `neo-flat` / `minimal-neutral` /29 `fluent`), and the `device` for mobile (`iphone` / `iphone-max` /30 `android` / `iphone-se`) here too.312. **Author one flow's specs as the pattern**, then fan the rest out against it.32 Getting the system right on the hardest flow is what makes parallel work33 safe.343. **Build.**35 ```bash36 node scripts/build-screens.mjs \37 --system <design-system.json> --specs <dir> --out <dir> [--img ../assets/img/]38 ```39 Every flow page carries a **journey filmstrip** above its screens — the40 flow's screens ordered by their `node` id (whole numbers ascending, then41 decimals under their parent; node-less screens sort last), each a small42 clickable card that jumps to its full section below. It renders in both43 layouts.4445 Add `--layout story` to build into a shared **design-story site** instead46 of a standalone set: pages land in `<out>/screens/<slug>.html` (one47 directory deeper — a relative `--img` base gets an extra `../`48 automatically) so they sit next to user-flow-maps' `flows/` output. Under49 `story`, each screen's Flow node value links back to50 `../flows/<flowSlug>.html#node-<id>`, and its Criteria chips link to51 `../coverage.html#ac-<id>` — both resolve once the sibling builds52 (`build-flowmaps.mjs --layout story` and `build-story.mjs`) have run;53 until then they just 404, no build depends on the other running first.544. **Verify.** See `references/verifying.md`. Check that no mock overflows its55 frame, no image is broken, and the page never scrolls sideways.565. **Tie flows and screens into one site.** Once both builds have written57 into the same `<out>` with `--layout story`, run58 ```bash59 node scripts/build-story.mjs --flows <flowspec dir|file> --screens <dir> --out <dir>60 ```61 to generate `<out>/index.html` (the design-story hub) and62 `<out>/coverage.html` (the acceptance-criteria coverage matrix). See63 "Design-story site" below for the full recipe.6465## What a spec carries6667Per screen: purpose, flow node, criteria, presentation kind, ordered regions68(each with its Material component and exact token names), every state with its69trigger and what changes, the platform calls, seeded content, accessibility70notes, implementation hints, grounding references, and anything left open.7172Full contract: `references/schema.md`.7374## Presentation drives the chrome7576How `nav.kind` and the surrounding chrome render depends on the `target`:7778- **mobile** — device frame, tab bars, nav bars, sheets, and the79 MD3-vs-platform calls. See `references/targets/mobile.md` for the full80 `nav.kind` table and the device library.81- **web** — responsive breakpoints (mobile-web / tablet / desktop), the app82 shell (top bar, side nav, dialogs), and the four selectable styles83 (Material UI, Neo-Flat, Minimal-Neutral, Fluent). See84 `references/targets/web.md`.8586Describe nav affordances plainly — `"share, favorite"` — and they render as87icons. Prose asides in parentheses are stripped, because a mock that prints88"(SF Symbols, not text buttons)" in its nav bar is documenting itself instead of89showing the screen.9091## Region types9293`appbar` `searchfield` `chips` `segmented` `hero` `gallery` `list` `card` `row`94`field` `datefield` `stepper` `price` `banner` `notice` `error` `cta`95`secondary-cta` `divider` `text` `footnote` `empty` `skeleton` `sheet-handle`96`map`9798Each renders in the mock and maps to a component in the inventory. Adding a type99means adding a renderer in `screenspec.js` *and* an inventory row — a type with100no inventory entry is a spec that can't be built.101102## Conventions that keep specs honest103104- **Name tokens exactly.** `surfaceContainerHigh`, not "light grey". A developer105 maps tokens to a palette; adjectives map to nothing.106- **State the platform call** wherever the design system and the platform107 disagree, with which won and why. An unstated blend is what produces visible108 seams.109- **Design the ugly states.** Empty, error, loading, disabled, and the110 domain-specific ones the criteria demand. A spec with only a happy path will111 be built with only a happy path.112- **A state that renders nothing is still a state.** If the correct behaviour is113 "discard silently, change nothing on screen", write that down — otherwise114 someone will add a spinner.115- **Mark known gaps as gaps.** A control whose destination doesn't exist gets116 specced as the gap it is, never filled with an invented screen.117- **Structure, not pixels.** No coordinates, no px widths. Say what is present118 and in what order; the renderer lays it out.119120## Styling121122Everything resolves through `design-system.json`'s tokens — colour roles become123CSS custom properties, the shape scale becomes radii. Swap the palette there and124every mock in the set follows. Light and dark are both generated.125126## Design-story site127128The end-to-end recipe that turns the two skills' separate outputs into one129navigable site a reviewer walks top to bottom — Problem → Journey → Screens →130Coverage, everything cross-linked:131132```bash133node <user-flow-maps>/scripts/build-flowmaps.mjs <flowspec.json> \134 --out <dir> --layout story --screens <screens dir>135136node scripts/build-screens.mjs \137 --system <design-system.json> --specs <screens dir> --out <dir> --layout story138139node scripts/build-story.mjs \140 --flows <flowspec.json|dir> --screens <screens dir> --out <dir> [--system <design-system.json>]141```142143All three point `--out` at the **same directory**. Order doesn't matter for144correctness (each build degrades to a dead link, never a failure, if a145sibling hasn't run yet), but running `build-story.mjs` last means its hub and146coverage page reflect what's actually on disk. The result:147148```149<dir>/150 index.html # the hub: Problem, Journey, Screens, Coverage151 coverage.html # every AC id x which node(s)/screen(s)/state(s) realize it152 flows/<slug>.html153 screens/<slug>.html154```155156Open `<dir>/index.html`. It links to each `flows/<slug>.html` (the journey),157groups screen links by flow in journey order, and summarizes coverage with a158link to the full matrix — it never re-renders a mock or a poster, only links159to the pages the other two builds produced.160161## Files162163| Path | What it is |164|---|---|165| `scripts/screenspec.js` | Mobile renderer: tokens, region renderers, device mock, state resolution. Browser and Node. |166| `scripts/screenspec.web.js` | Web renderer: responsive breakpoints, app shell, region renderers for the web target. Browser and Node. |167| `scripts/styles.js` | The four selectable web styles (Material UI, Neo-Flat, Minimal-Neutral, Fluent) — palette and token overrides per style. |168| `scripts/build-screens.mjs` | CLI: design system + specs → linked HTML pages, one per flow (with a journey filmstrip), plus a design-system index. `--layout story` cross-links into a shared design-story site. |169| `scripts/journey.mjs` | `journeyOrder(screens)` — sorts a flow's screens into journey order by `node` id; backs the filmstrip and the hub's per-flow screen groups. |170| `scripts/build-story.mjs` | CLI: flow spec(s) + screen spec(s) → `index.html` (design-story hub) and `coverage.html` (AC coverage matrix). Reads both builds' inputs, renders neither's mocks — only links. |171| `references/schema.md` | The spec contract, field by field. |172| `references/verifying.md` | What to check before calling the set done. |173| `references/coverage-both-ways.md` | Node→screen *and* screen→node coverage. Run after any flow-map edit — a retired node leaves an orphaned screen that a one-directional check cannot see. |