Spec mockups
This skill says how to draw. lw-spec-write says where the spec goes and how the page renders.
Product facts
The adapter folder comes from the config. Read it first:
ah config get harness.adapterDir
Then read <adapterDir>/spec-mockup/. The project owns it. It names:
| Fact | Example |
|---|---|
| The token files of each app, in order | apps/console/src/styles/tokens.css |
| The selectors of the light and the dark values | :root, .dark |
| The lucide icon folder | node_modules/lucide-react/dist/esm/icons |
| The navigation of each app with a sidebar, and where it comes from | the route table of the console |
| The source of real copy | the i18n catalogue of the app |
When the adapter is missing, ask the user for the token file before you draw. Never invent a colour.
The format
Generated SVG, drawn with one library: boards.py beside this file. Each spec has
one script, docs/specs/assets/<date>-<topic>-boards.py. It imports the library and writes into
docs/specs/assets/<date>-<topic>/. Never edit a generated SVG by hand: change the script and run
it again. When the library lacks a piece, add it to the project's own helper module beside the
script, so the next spec has it too.
example.py is a worked board: a page on desktop and on a phone, with pins and a legend. Run it to see the shape:
python3 .claude/skills/lw-spec-mockup/example.py <scratchpad>/mock --render
A spec script starts like this:
import sys
from pathlib import Path
ROOT = Path(__file__).resolve().parents[3]
sys.path.insert(0, str(ROOT / ".claude/skills/lw-spec-mockup"))
from boards import GUTTER, Board, render, tokens
t = tokens("apps/console/src/styles/tokens.css", "light")
b = Board(1440 + 2 * GUTTER, 1200, icons="node_modules/lucide-react/dist/esm/icons")
b.title("Dashboard: the empty state")
f = b.sidebar_app(
GUTTER,
160,
t["bg"],
t["sidebar"],
t["ink"],
t["muted"],
t["active"],
["Dashboard"],
"Dashboard",
"Console",
)
b.pin(f.x + f.left + 24, f.top + 30, 1)
b.legend(
GUTTER, 1100, [(1, "The empty state", "No widget until the product names one.", "R1")], 1440
)
b.save("docs/specs/assets/<date>-<topic>/desktop-empty.svg")
| Call | Draws |
|---|---|
tokens(files, theme, base=, dark=, overlay=) | The resolved --* values of the token files. t["accent"]. A missing token raises. overlay is a proposed restyle |
phone(x, y, bg, ink, surface, url), desktop(x, y, bg, url) | A phone (web or app), a browser window |
sidebar_app(x, y, ...) | An app with a left sidebar, such as a console. Copy nav from the app's route table |
screenshot(path, x, y), crop(path, ...) | A real capture of the shipped screen, or a region of it, for the Today column |
icon(name, x, y, size, color) | A lucide glyph from the board's icons folder |
image(path, ...), embed_font(family, path) | Real art and a brand face from the repository |
pin, legend, caption, column_head, row_label, title | The notes. A legend note longer than two lines raises |
render(svg, png, w, h) | The headless Chrome screenshot for the check below |
Fidelity
- Colours are tokens, through
tokens(). A hex value in a spec script is wrong, except for art that the token file does not hold. - Copy is real. Use the string of the app's catalogue when it exists. A new string is short: a label, a value, an empty state, an error or a one-line hint.
- Data is realistic. Use plausible values, and a long one where one can occur.
Canvas
| Surface | Frame |
|---|---|
| Phone | 390 x 844, 48 px gutters |
| Desktop | 1440 x 900 |
| Board | as wide as its frames, as high as its rows need |
What a spec draws
- Today, then target. "Today" is a real screenshot of the shipped screen (
today-*.png). "Target" is the SVG. Put them side by side in a Markdown table. - One row per state that applies: default, empty, loading, error, long text, the smallest and the largest content.
- Pins and a legend. A numbered pin on each element that changes. Under the frame, a numbered legend: a bold head, two lines at most, and the spec ID it serves.
- A component list in the spec beside the boards: each element, the component it reuses, and each new component with its reason.
Names
docs/specs/assets/<YYYY-MM-DD>-<topic>-boards.py
docs/specs/assets/<YYYY-MM-DD>-<topic>/<surface>-<state>.svg
docs/specs/assets/<YYYY-MM-DD>-<topic>/today-<surface>-<state>.png
The spec names the basename of every file it holds in assets/.
Look at every board before you commit it
A defect of a board is invisible in its source and clear in the picture. Render each board and
read the PNG with the Read tool. Write the PNG to the scratchpad, not to docs/.
- No text runs past its frame, and no pin covers a value.
- Each string in the drawn UI is a label, a value, an empty state, an error or a hint.
- No placeholder art, and no colour picked by eye.
- Each state and surface that the spec touches has its frame.
- Today and target sit side by side.
render needs google-chrome or chromium on PATH.
After the build
The build must match the approved board. Capture the built screen and put it beside the board. A difference is a defect of the build, not a reason to draw the board again.