Post-it
Post-it is a place to write things down, where who can read what is the product rather than a setting on the side of it. You write Markdown in spaces, organise it in folders, and share a page or a whole folder with a person, with a team, or with anyone at all. A page you have not been given is not merely hidden from you: it is indistinguishable from a page that does not exist.
A page is usually Markdown, and can also be an HTML document or a JSON file, either written here or uploaded as it is. JSON is shown as a tree you can fold, with the file itself underneath.
An HTML page is the one thing here that works differently. Its bytes are a file rather than a row, and it carries an address you can send to somebody with no account at all — which is what a mockup is for. The document runs in a frame of its own, with no reach into Post-it, and anybody holding the link can open it. It is the only thing here readable without being given, and the page says so next to the link.
The other half is that you can connect it to Claude, so the things you have written are available in conversation without copying them anywhere. That is what the rest of this page is about.
Connecting Post-it to Claude
1. Make a token
Sign in, then go to Your spaces and follow Connect Post-it to Claude, or go straight to /settings/mcp. Give the token a name you will recognise in six months, like the machine it is going on.
The token is shown once and never again. Only a hash of it is stored, so nobody, including us, can recover it. If you lose it, revoke it and make another; that takes ten seconds and is the right instinct. Revoking takes effect on the very next request.
By default a token reaches everything you can reach. You can pin one to a single space at creation, which is worth doing for a machine you trust less than your own.
2. Point Claude at it
Your endpoint is:
https://post.staging.maqsoodlabs.com/api/mcpClaude Code. Run this in a terminal, or put the JSON in .mcp.json at the root of a project.
claude mcp add --transport http postit https://post.staging.maqsoodlabs.com/api/mcp \
--header "Authorization: Bearer post_your_token_here"{
"mcpServers": {
"postit": {
"type": "http",
"url": "https://post.staging.maqsoodlabs.com/api/mcp",
"headers": {
"Authorization": "Bearer post_your_token_here"
}
}
}
}The Claude apps, desktop and web. Settings, then Connectors, then Add custom connector. That dialog takes a URL and nothing else, so for these the token goes in the URL itself: https://post.staging.maqsoodlabs.com/api/mcp/your-token, which the token screen gives you ready to paste.
That form is deliberately the weaker one. A token in a URL is in every HTTP log that records the path, in whatever the client stores for the connection, and anywhere the URL is pasted; a token in a header is in none of those. Use it only where a header is not on offer, and prefer a token pinned to a single space so that a leak costs one space rather than an account.
The API. Three things are needed and it is easy to give two: the beta header, the server in mcp_servers, and a matching mcp_toolset entry in tools. Leaving the toolset out does not quietly ignore the server, it makes the request invalid.
curl https://api.anthropic.com/v1/messages \
-H "content-type: application/json" \
-H "x-api-key: $ANTHROPIC_API_KEY" \
-H "anthropic-version: 2023-06-01" \
-H "anthropic-beta: mcp-client-2025-11-20" \
-d '{
"model": "claude-opus-5",
"max_tokens": 2048,
"messages": [
{
"role": "user",
"content": "What is on my todo list?"
}
],
"mcp_servers": [
{
"type": "url",
"url": "https://post.staging.maqsoodlabs.com/api/mcp",
"name": "postit",
"authorization_token": "post_your_token_here"
}
],
"tools": [
{
"type": "mcp_toolset",
"mcp_server_name": "postit"
}
]
}'What a connected Claude can and cannot reach
A token acts as you. Every request it makes is answered by the same rules that answer your requests in the browser, so a connected Claude sees exactly what you see and never more. Share a folder with yourself tomorrow and it appears; have it revoked and it disappears, with no cache to go stale in between.
It can:
list_spaces,list_treeto see what is in one,searchwithin it, andread_pageby path or idlist_skillsandget_skill, so it can find and follow the skills you have writtencreate_folderandcreate_page, so it can build structure and not just a flat list, andupdate_pageguarded by a version number, so it cannot overwrite an edit you made while it was thinkingattach_file, to bring in a file it already has: Markdown, an HTML document or a JSON file, named after the file and typed from its extension, withappend_to_pagefor one too large to pass in a single callask_for_review,approve_pageandclear_review, for documents whose author wants one. Approving through a token is you approving, so ask for it deliberately rather than leaving it to a tidy-uplist_backlinks, to see what points at a pagelist_commentsandadd_comment, to read and join the conversation on a page
It cannot:
- delete or move anything
- change who can see anything
- reach a page nobody has given you, whatever it is asked to do
That list is short on purpose. A token is a long-lived credential sitting on the internet, and one that can only add is a far smaller problem to have leaked than one that can remove. The dangerous things are still available, deliberately, in the browser.
Articles and skills
Every page in Post-it is an article or a skill. An article is prose: notes, a decision, a write-up. A skill is a Markdown file written as instructions for Claude to follow, in the same shape Claude uses elsewhere, which means Post-it doubles as a place to keep them.
The distinction earns its keep the moment you ask for your skills:
list_skillsreturns your skills and nothing else, so "what can you do for me here" does not also return every meeting note.get_skillreturns the instructions with the metadata stripped, ready to follow.
A skill worth having:
- Has a name and a description in its frontmatter. The description is what a client reads to decide whether the skill is relevant, so it should say when to use it, not what it is called.
- Says what to do, not what the topic is. "Read the runbook before starting, every time" beats "runbooks are important".
- Includes a worked example. A skill nobody can picture using does not get used.
- Names the failure it exists to prevent. That is usually the reason somebody wrote it, and it is the part that makes the instruction stick.
Post-it will not stop you saving a skill with no description. It will say so and save it anyway: losing what you wrote over a formatting detail is a much worse outcome than an incomplete skill.
Skillsets, and installing them anywhere
A space whose contents are skills can be marked a skillset, from the Skillset button in its header. The mark changes nothing about who can read anything. A skillset is a space: the same members, the same teams, the same sharing, and a page nobody gave you is still a page you cannot see. What it adds is that the skills in it can be fetched as files.
The format is the one the wider ecosystem settled on: a folder per skill, each holding a SKILL.md whose frontmatter carries a name and a description. Post-it has stored exactly that since skills existed. So the tools that install skills can install from here:
npx skills add https://post.staging.maqsoodlabs.com/k/YOUR_TOKEN/your-skillset.tar.gzThe token in that address is an ordinary MCP token, and it is you: it reaches exactly the skills its owner can reach and no more. Two people running the same command get different skills, because they were given different things, and somebody who was given nothing gets the same 404 as if the skillset did not exist. Nothing in the serving code knows the difference between them — it is the same rule that decides what you see in a browser.
Which is also the warning. A secret in an address is in every log that records addresses, in whatever the installer writes to disk, and in any window the URL is later pasted into. Pin the token to the one skillset when you make it, so a leaked address costs that skillset rather than your account.
The folder each skill installs into is named from its frontmatter rather than from the page, because the standard asks for a skill and its folder to agree. A page called "Invoicing (v2, final)" whose frontmatter says name: monthly-invoicing installs as monthly-invoicing/. One skill can be fetched on its own at /k/YOUR_TOKEN/your-skillset/monthly-invoicing/SKILL.md, and /k/YOUR_TOKEN on its own lists what that token can reach.
What a skillset does not carry yet is the optional scripts/ folder that the format allows. Post-it has no file type that is meant to be executed, and adding one is not a small decision. Skills here are instructions and references.
Six skills to start with
Copy any of these into a space, mark it as a skill, and it is available to your Claude immediately. They are meant to be edited: the house style one in particular is worth nothing until it describes how you actually write.
Chat Context
File what a conversation decided into Post-it, and pick it back up in a later session.
---
name: Chat Context
description: Record what a conversation established in Post-it, and retrieve it at the start of a later one
---
# Chat Context
What people lose between conversations is rarely *what* was decided. It is
*why*, and what was ruled out. This skill files that, and reads it back.
## At the start of a conversation
Search Post-it for context before assuming there is none:
1. `search` the space for the project or topic by name.
2. `read_page` anything that looks relevant.
3. Say what you found in one sentence, so the reader knows what you are
working from and can correct it.
If nothing matches, say so rather than inventing background.
## At the end of a conversation, or when asked to file it
Write a page under `context/<topic>` containing:
- **What we settled**, one line each.
- **Why**, including the alternative that was rejected and the reason.
- **Still open**, questions that were raised and not answered.
- **Where things are**, links or paths to anything produced.
Keep it short enough that somebody will actually read it in six months. If a
page already exists for the topic, `read_page` it first and `update_page` with
the version number it gave you, so you extend the record rather than replacing
somebody else's account of it.
## Worked example
> **Reader:** We have been going back and forth on the export format. File this.
You write `context/export-format`:
```markdown
# Export format
## What we settled
- Exports are NDJSON, one record per line.
## Why
- CSV was rejected: the nested attachment metadata does not flatten without
losing the association between a file and its page.
- A single JSON array was rejected: exports are expected to reach a few
hundred megabytes, and a consumer should be able to stream them.
## Still open
- Whether deleted records appear with a tombstone or are simply absent.
## Where things are
- Prototype writer at [[Export Writer]].
```
Three months later somebody asks why the export is not CSV. The answer is on
the page, in the words of the people who made the decision.
Todo List
Keep todos as a checklist in a page. Claude adds items, reads what is outstanding, and ticks things off.
---
name: Todo List
description: Read and update the reader's todo list, kept as a Markdown checklist in Post-it
---
# Todo List
Todos live in a normal page as GitHub-flavoured task lists. That format is the
point: it renders as checkboxes in Post-it, stays readable as plain text
anywhere else, and a person editing it by hand and Claude editing it through
the API produce exactly the same thing.
## The format
```markdown
## Open
- [ ] Book the venue @sam #launch
- [ ] Draft the announcement ~2026-10-01
## Done
- [x] Pick a date ~2026-09-04
```
- `- [ ]` is open, `- [x]` is done. Nothing else counts as a todo.
- `@name` is who it is for. `#tag` groups related items. `~YYYY-MM-DD` is a
date: when it is due while open, when it was finished once done.
- Completed items move to the **Done** heading rather than being deleted, so the
page answers "what happened last month" as well as "what is left".
## Reading
`read_page` the todo page and report only the open items unless asked
otherwise. Group by `#tag` when there are more than a handful. If the reader
asks what is overdue, compare `~` dates against today and say so plainly.
## Writing
Adding an item or ticking one off is an `update_page`, and `update_page`
requires the version number that `read_page` returned.
**Always read immediately before you write.** If the write is refused because
somebody else saved in between, do not retry blindly with the same text: read
the page again, apply your change to the version that is now there, and write
that. Retrying without re-reading is how one person's todo quietly disappears.
Never rewrite items you were not asked to touch. Preserve their wording,
including the parts you would have phrased differently.
## Worked example
> **Reader:** Add "chase the printer quote" for me, and mark the venue booked.
You `read_page` `todos`, which comes back at version 7. You write back at
version 7 with the venue line moved under **Done** with today's date, and the
new item appended under **Open**. You reply:
> Done. Two open: chase the printer quote, draft the announcement (due 1 Oct).
If the save had been refused, you would read again, find that somebody had
added "confirm catering" in the meantime, and write a version that keeps both.
Decision Log
Record a decision with its alternatives and what would make you revisit it, then answer 'why do we do it this way' from the record.
---
name: Decision Log
description: Record and retrieve decisions, with the reasoning and the conditions that would reopen them
---
# Decision Log
A decision without its reasoning is a rule nobody can safely change. This keeps
the reasoning attached.
## Recording one
One page per decision, under `decisions/`, named for the decision rather than
the date. Include:
- **Decision.** One sentence, in the present tense: "We use X."
- **Date** and **who was in the room.**
- **Context.** What made this need deciding at all.
- **Alternatives.** What else was considered, and the specific reason each was
not chosen. An alternative with no stated reason is not an alternative, it is
a gesture.
- **Consequences.** What this makes easy, and what it makes hard. Both.
- **Revisit if.** The concrete thing that would make this the wrong call. This
is the most valuable line on the page and the one most often left out.
## Answering from it
When somebody asks why something is the way it is, `search` `decisions/`
before answering from your own reasoning. Quote the decision and its date. If
the "revisit if" condition now looks true, say so: that is the whole reason it
was written down.
If there is no record, say there is no record. Do not reconstruct a
justification, however plausible; a reconstructed reason is indistinguishable
from a real one to the next reader, and it is not the same thing.
## Worked example
`decisions/postgres-over-search-service`:
```markdown
# We search in Postgres rather than running a search service
**Date:** 2026-09-04
**Present:** Asim, Noreen
## Context
Search has to be filtered by permissions, and every result must already be
readable by the person searching.
## Alternatives
- A dedicated search service. Rejected: the index would be a second copy of
the permission model, and the two would drift. A stale index is a leak.
- Client-side search. Rejected: it requires shipping the corpus to the browser.
## Consequences
Easy: results are filtered by the same rules as everything else, for free.
Hard: no fuzzy matching or typo tolerance beyond what Postgres offers.
## Revisit if
The corpus outgrows what a single Postgres instance ranks quickly, or people
start complaining about typo tolerance more than about relevance.
```
A year later, somebody proposes adding a search service. The page says exactly
what would have to be true first.
House Style
Teach Claude how you write, so drafts come back sounding like you rather than like a press release.
---
name: House Style
description: How we write, so drafts come back sounding like us
---
# House Style
Edit this page until it describes how *you* actually write. The example rules
below are a starting point, not a prescription: the value is in the specificity,
so replace anything that is not true of you.
## Rules
- Say the thing. Put the point in the first sentence, not after a paragraph of
throat-clearing.
- Prefer short words. "Use", not "utilise". "Help", not "facilitate".
- No filler openers. Never begin with "In today's fast-paced world" or "It is
worth noting that".
- Concrete over abstract. Name the customer, the number, the date.
- Admit uncertainty in plain words: "I think", "we do not know yet". Do not
hedge with "may potentially".
- One idea per paragraph. If a paragraph needs a "furthermore", it is two
paragraphs.
- Active voice, except where the actor genuinely does not matter.
- No exclamation marks. No emoji.
## Words we avoid
leverage, synergy, robust, seamless, delve, journey, unlock, empower,
best-in-class, game-changing, at the end of the day.
## How to use this
When asked to draft anything that other people will read, `get_skill` this
first and follow it. When editing something the reader wrote, respect their
voice: fix what is wrong, do not rewrite what is merely different from how you
would have put it.
## Worked example
> **Draft:** "We are excited to announce that we have leveraged our robust new
> platform to unlock a seamless experience for our valued customers!"
> **In house style:** "Billing now runs on the new platform. Invoices arrive
> the morning after a period closes instead of three days later."
The second one says what changed. The first one says nothing.
Onboarding a Colleague
Share a folder with somebody and their own Claude picks up the project's context, through their own token.
---
name: Onboarding a Colleague
description: Bring somebody up to speed on a project from what is already written down
---
# Onboarding a Colleague
The useful version of onboarding is not a document written for newcomers and
then left to rot. It is the project's real notes, made readable by the new
person, and a Claude that can read them on their behalf.
## Bringing somebody in
1. Share the project folder with them, as **viewer** if they are learning and
**editor** if they will be contributing. Sharing a folder reaches everything
inside it.
2. Point them at `/settings/mcp` to create their own token.
That is the whole setup. Their token acts as them, so they see the folder you
shared and nothing else in your space. There is no separate copy of the
material to keep in step, and nothing to revoke later except the share itself.
## What their Claude should do first
With this skill loaded, on being asked to get up to speed on a project:
1. `list_spaces`, then `search` the space for the project name.
2. Read, in this order: anything under `decisions/`, anything under
`context/`, then the project's own pages.
3. Produce a short brief: what the project is, what has been decided and why,
what is still open, and who to ask about what.
4. **List what you could not find.** A gap named on day one gets filled. A gap
papered over becomes a wrong assumption three weeks later.
## Worked example
> **New joiner:** Get me up to speed on Billing.
> **Claude:** Billing moved to the new platform on 4 September. Invoices are
> generated the morning after a period closes. Two decisions worth knowing:
> NDJSON for exports, because the attachment metadata does not flatten to CSV;
> and Postgres for search rather than a separate service, to keep permissions
> in one place. Still open: whether deleted records get a tombstone.
>
> I could not find anything about how refunds are handled, or who owns the
> relationship with the payment provider. Worth asking before you touch either.
Runbooks
Operational procedures written once and followed consistently, read live rather than from a stale copy.
---
name: Runbooks
description: Follow an operational procedure from Post-it, reading the current version rather than a remembered one
---
# Runbooks
A runbook is worth having because it is followed the same way every time,
including at three in the morning by somebody who has never done it before.
## Following one
**Always `read_page` the runbook before starting, every time.** Do not work
from a copy you read earlier in the conversation, and do not work from memory
of a previous run. Procedures change, and the whole point is that the current
version is the one that gets followed.
Then:
1. Say which runbook you are following and its last-updated date.
2. Work through the steps in order. Do not skip a step because it looks
unnecessary; if it looks unnecessary, say so and ask.
3. Stop at anything the runbook marks as needing a human decision. Marked
steps are marked because somebody was burned.
4. At the end, report what you did, what you observed, and anything that
differed from what the runbook said would happen.
## Writing one
- One page per procedure, under `runbooks/`.
- Start with **when to use this** and **when not to**.
- Numbered steps, one action each, with the exact command or the exact screen.
- Mark steps that need a human decision with **STOP**.
- End with **how to tell it worked** and **how to undo it**.
## Keeping them true
When a run turns up something the runbook got wrong, fix the runbook while it
is fresh: `read_page`, then `update_page` with the version you were given. A
runbook that is wrong once is worse than no runbook, because it will be
followed anyway.
## Worked example
`runbooks/restore-a-deleted-space`:
```markdown
# Restore a deleted space
**Use when:** somebody deleted a space and wants it back within 7 days.
**Do not use when:** they want a single page back. See [[Restore a Page]].
1. Confirm the request came from the space owner. **STOP** if it did not.
2. Find the deletion in the audit log: \`select * from audit where ...\`
3. **STOP.** Confirm the restore window with whoever is on call.
4. Run the restore: \`select restore_space('<id>')\`
**How to tell it worked:** the owner can open /s/<slug> and the tree matches
the node count from step 2.
**How to undo it:** delete the space again. Restoring is additive.
```
At three in the morning, the two **STOP** lines are the entire value of the
page.
Wave Design
For Claude Design: start here. Says which Wave skill comes next, from DESIGN.md to review.
---
name: Wave Design
description: Start here for any Wave work in Post-it (design systems, features, HTML mockups, dry runs, uploads, prototypes and review). Says which Wave skill to use next (Wave Brief, Wave Design System, Wave Feature, Wave Review). Use before creating, changing or uploading any HTML mockup or component.
---
# Wave Design
Wave is how Post-it reviews HTML mockups and hands them to Claude Code. **The HTML
is the spec**: what every element is, says and does lives on it as
`data-wave-*` attributes, checked against the project's design system.
The work goes in four stages, each with its own skill. Load the one you need
with `get_skill` (space postit, path `skills/<name>`) and follow it exactly.
| Stage | Skill | Makes | When |
| --- | --- | --- | --- |
| 1 | `wave-brief` | DESIGN.md at the project root | Once per project, first |
| 2 | `wave-design-system` | Tokens and component specimens | Once per project, after the brief; again for a new component |
| 3 | `wave-feature` | FEATURE.md, then the screens with their attributes | Each feature |
| 4 | `wave-review` | The few questions left, preflight, upload, prototype, review | Each feature, after stage 3 |
| Figma | `wave-figma` | The whole flow from a Figma file: its own three stages (brief, design system, feature), run for an engineer | Instead of everything above when the design is in Figma |
## Always start here
1. Ask the designer **which project** and **which feature** this is for.
- Find it with `list_spaces` and `list_tree` (projects and features are marked).
- No project yet: `create_folder` with `project: true` (it creates design-system/ and
design-system/components). No feature yet: `create_folder` inside the project with
`flow: true`.
2. `wave_get_brief` (kind design, the project). No DESIGN.md yet, or it
lists problems: **Wave Brief** first.
3. `get_catalogue` for the project. No approved catalogue (no components,
or no valid token file): **Wave Design System** next.
4. For a feature: `wave_get_brief` (kind feature). No FEATURE.md yet: **Wave
Feature** (it writes the brief with the designer, then the screens).
5. The design is in Figma (a Figma link, or "from Figma"): load **Wave Figma**
and follow it instead of this list. It runs its own three stages with the
engineer and refuses a file Wave cannot convert exactly.
6. Screens designed, or the designer brings a mockup made elsewhere: **Wave
Review**. "Wave dry run" also means Wave Review (it saves the question
sheet and uploads nothing).
7. "Make it a prototype" or "share the prototype": Wave Review, prototype
section.
Nothing is uploaded before the designer has approved DESIGN.md and the
catalogue. To build an approved flow in code, use **Wave Build**.
## What is never asked
Every element inherits, in this order, and anything inherited is not a
question:
1. **Its own HTML** (`data-wave-*`, native attributes).
2. **FEATURE.md**: the fields it writes (`data-wave-field`: rules, options,
default, shown-when), the data it shows (`data-wave-bind`: type, source,
empty, format), the action it takes (trigger, effects, destinations,
confirm, tracking) and the screen's route, title and access.
3. **Its component** in the catalogue: states, variants, responsive
behaviour, events; parts inside a component instance belong to it.
4. **DESIGN.md**: copy status and where copy lives, who can see things,
analytics, flags, form behaviour, empty values, overflow, icons,
viewports, language.
5. **What Wave knows for certain**: names (slugs), element types, a submit
button's trigger, the action name, "none" for a control that only
navigates.
When answers are applied, what came from FEATURE.md and what Wave is certain
of is written into the HTML; DESIGN.md and the catalogue stay the policy.
Wave Brief
For Claude Design: interview the designer and write the project's DESIGN.md, the defaults every screen inherits.
---
name: Wave Brief
description: Interview the designer and write the project's DESIGN.md (its defaults and design language) in Post-it, so Wave never asks the same thing per element. Use once per project, before the design system and any screen.
---
# Wave Brief: DESIGN.md
DESIGN.md sits at the project root. Its **front matter** is the defaults
every element in the project inherits; its **prose** is the design language
you follow whenever you design for this project. A complete DESIGN.md removes
most of Wave's questions before anything is drawn.
## Steps
1. `wave_get_brief` (kind design, id = the project). It returns the saved
file, or a template, and what is missing.
2. **Read before asking.** Take everything you can from what the designer
already gave you: their prompt, brand notes, existing screens, the token
file. Fill those in first; never ask what you already know.
3. **Interview for the rest, grouped**, a few questions at a time, each with
your proposed answer to confirm or change:
- Product: what it is, who uses it, the platforms and widths
(`viewports`), the language (`lang`).
- Content: is the copy in the designs final, draft or placeholder
(`content.copy`)? Where will copy live: code, a CMS, translation keys
(`content.source`)?
- Access: who can open a screen by default: public, signed-in, a role
(`access`)? Can everyone see every element (`element-access`)?
- Analytics: are controls tracked, and with what naming convention
(`analytics.controls`: none or e.g. object_action)? Page views
(`analytics.page-views`)?
- Feature flags by default (`flags`: none, or the flag system).
- Forms: when errors show (`forms.validate-on`: submit, blur, change);
warn on leaving with unsaved changes (`forms.dirty-guard`).
- Data: what data-driven text shows with no value (`data.empty`: hide,
a dash, text); long text (`data.overflow`: wrap, truncate, clamp:2).
- Icons: the library (lucide, material) or inline SVG (`icons`).
- Responsive: what sections and cards do on small screens by default
(`responsive`: stack, hide, collapse, scroll).
- Links to other sites (`links.external`: _blank or _self); how dialogs
and toasts close (`overlays`).
4. **Write the prose**, one section each, in the designer's words where you
can: Product, Voice and copy, Visual language, Layout and breakpoints, Components, Interaction and states, Forms, Accessibility, Content and data, Analytics. The visual language is concrete:
colours with their hex values and roles, type families, sizes and
weights, spacing scale, corner radii, shadows, motion. Components names
every component the product needs. Forms carries the UX rules (when to
use chips instead of a select, when errors appear, what is never
disabled).
5. **Show the designer the whole file** and ask: "Is this right? Anything to
change?" Change it until they approve.
6. `wave_save_brief` (kind design). Fix anything it still lists and save
again.
7. Next: **Wave Design System** builds the tokens and components from it.
## The format
```markdown
---
wave: 1
name: Acme
lang: en
viewports: [390, 1280]
access: public # who opens a screen by default: public, signed-in, role:<name>
element-access: everyone
icons: inline # a library name (lucide, material) or inline
content:
copy: final # fixed text is final, draft or placeholder
source: code # code, cms or i18n
analytics:
controls: none # none, or a convention such as object_action
page-views: none
flags: none
responsive: stack # sections and cards on small screens: stack, hide, collapse, scroll
forms:
validate-on: submit # submit, blur or change
dirty-guard: off
data:
empty: hide # what data-driven text shows with no value
overflow: wrap
images:
fit: cover
links:
external: _blank
overlays:
modal: close-button escape
toast: auto:5
---
# Acme design
## Product
## Voice and copy
## Visual language
## Layout and breakpoints
## Components
## Interaction and states
## Forms
## Accessibility
## Content and data
## Analytics
```
Front matter values are what an element inherits when its own HTML says
nothing. An element can always override one (`data-wave-copy="draft"`,
`data-wave-track="checkout_started"`).
## What is never asked
Every element inherits, in this order, and anything inherited is not a
question:
1. **Its own HTML** (`data-wave-*`, native attributes).
2. **FEATURE.md**: the fields it writes (`data-wave-field`: rules, options,
default, shown-when), the data it shows (`data-wave-bind`: type, source,
empty, format), the action it takes (trigger, effects, destinations,
confirm, tracking) and the screen's route, title and access.
3. **Its component** in the catalogue: states, variants, responsive
behaviour, events; parts inside a component instance belong to it.
4. **DESIGN.md**: copy status and where copy lives, who can see things,
analytics, flags, form behaviour, empty values, overflow, icons,
viewports, language.
5. **What Wave knows for certain**: names (slugs), element types, a submit
button's trigger, the action name, "none" for a control that only
navigates.
When answers are applied, what came from FEATURE.md and what Wave is certain
of is written into the HTML; DESIGN.md and the catalogue stay the policy.
Wave Design System
For Claude Design: build the project's tokens and approved component specimens from DESIGN.md.
---
name: Wave Design System
description: Build and upload a project's design system to Post-it (DTCG tokens and one approved HTML specimen per component, with variants, states, events and responsive behaviour) from DESIGN.md. Use after Wave Brief, and whenever a design needs a new component or variant.
---
# Wave Design System
The catalogue is the project's tokens and components, approved by the
designer. Every instance on a screen inherits its component's states,
variants, events and responsive behaviour, so they are never asked per
element.
## Steps
1. `wave_get_brief` (kind design). Without a saved DESIGN.md, run **Wave
Brief** first. Take everything you can from it: the visual language gives
the tokens, the Components section the list of components, Interaction and
states the states, Layout the responsive behaviour.
2. **Tokens.** Every colour, size, spacing, radius, border width, shadow,
font family, font weight, line height, letter spacing, duration, easing,
opacity and z-index the designs use, as one W3C DTCG JSON file: every
token has `$type` (own or inherited), dimensions are
`{"value": 1, "unit": "rem"}` (never px; 1px is 0.0625rem), aliases point
at real tokens. Show the designer the list grouped by type and ask: "Are
these the right names and values? Anything missing or duplicated?"
Publish it as the JSON page `design-system/tokens`.
3. **Components.** For each component in DESIGN.md (and any the designs
show), propose, then confirm with the designer, grouped:
- name and element type (button, textInput, card...); whether two
similar things are one component with variants or two components;
- variants, and **every state** it has (default, hover, focus, filled,
valid, warning, error, disabled, loading, selected...);
- **events**: what using it means, e.g. `{"select": "change"}` for a
chip, `{"press": "click"}` for a button;
- **responsive**: what it does on small screens (stack, full-width,
hide, scroll);
- anatomy (label, icon, helper text) and accessibility notes.
4. **Specimens.** One HTML page per component: head with
`<meta name="wave:spec" content="1">`, `<meta name="wave:component" content="Button">`
and a `<script type="application/wave-component+json" id="wave-component">`
holding `{"type","description","variants","states","events","responsive","anatomy","a11y","status"}`;
body drawing **every variant and every state**, each example marked
`data-wave-component`, `data-wave-variant` and, for states,
`data-wave-state`. Styles use only token variables.
`wave_extract_component` makes a first specimen from an element on a
screen.
5. `preflight_html` on each specimen (target = the project); fix everything
it reports.
6. **Designer approval.** Show the tokens and every component with its
variants and states. When the designer approves, set `"status": "approved"`
in each definition and publish the specimens into
`design-system/components`.
- Publish each screen into the feature folder with `attach_file` (`<screen-slug>.html`)
or `create_page` (`content_type: "html"`, `parent_id` = the feature). For a new
version, `read_page` then `update_page` with its version. Specimens go into
`design-system/components` the same way.
- After saving, `check_screen` shows what Wave still finds missing on the uploaded file.
7. `wave_design_system_page` writes the design-system page (every
component's design-system id, its variants' ids and its type) and the same
table as JSON (`design-system-ids`) next to it, from the specimens. Never
write that table by hand; run it again whenever a specimen is published,
changed or approved.
8. Confirm with `get_catalogue`. Next: **Wave Feature** for the first
feature.
## A new component later
When a screen needs something the catalogue lacks, ask the designer: "Is
this a new component, or a new variant of X?" Yes: steps 3 to 7 for it. No:
rebuild it from the existing component.
Wave Feature
For Claude Design: write a feature's FEATURE.md from the prompt, then generate its screens with Wave attributes in place.
---
name: Wave Feature
description: Turn the designer's prompt for a feature into FEATURE.md (screens, fields, data, actions) in Post-it, then generate the feature's HTML screens with every data-wave-* attribute already in place, reusing the catalogue exactly. Use for each new feature, after the design system is approved.
---
# Wave Feature
A feature is designed from its brief. FEATURE.md says what the screens are,
what people fill in, what data they see and what every action does; the
screens are then generated from it with their attributes, so almost nothing
is left to ask.
## 1. FEATURE.md, from the prompt
1. `wave_get_brief` (kind design) and `get_catalogue`: the defaults and the
components you design with. `wave_get_brief` (kind feature, the feature
folder) for the brief so far (or a template).
2. **Extract from the prompt first.** A good prompt already says most of it:
- **screens**: one per step or state the prompt describes, each with a
slug, title and route (`/start`, `/start/project`), and access if it
differs from DESIGN.md;
- **fields**: every input, as a data path (`lead/email`) with its type,
rules (`required; pattern:email`), options for choices (chips, cards,
selects), default, and `visible-if` for conditional fields ("Other
opens a text field" is `visible-if: lead/role == Other`);
- **data**: everything a screen shows that is not fixed copy (a name, a
recap, a price), with type, source, description, and empty/format when
it matters;
- **actions**: every button or link that does something, as an id
(`lead/save-about`) with its screen, trigger, effects
(`api/leads/save`, `email/confirmation`), destination on success
(`to: screen:your-project`) and on failure
(`failure: node:about-you/error-count`), and confirm, feedback,
tracking or disabled-if when they apply. A screen's submit action
belongs to its submit buttons; name others with `on: [slug]`.
3. **Ask only what the prompt leaves open**, grouped, with your proposal:
where a failure goes, what a dead end links to, which effects an action
has. Never ask what DESIGN.md already sets.
4. Show the designer FEATURE.md and ask for their approval, then
`wave_save_brief` (kind feature). Fix what it lists.
```markdown
---
wave: 1
feature: checkout
name: Checkout
screens:
first-screen: { title: First screen, route: /path }
fields:
# path: { type, validate, options, default, visible-if, label }
data:
# path: { type, source, description, empty, format }
actions:
# id: { screen, on: [slug], trigger, effect, to, failure, confirm, feedback, track }
---
# Checkout
## Goal
## Flow
## Rules
## Outcomes
```
## 2. The screens, with their attributes
Generate each screen so the HTML is already the spec:
1. **Components exactly as the catalogue draws them**: read each specimen
(`read_page`) and copy its markup and classes; mark each instance
`data-wave-component` and `data-wave-variant`. Never restyle one.
2. **Ids**: every meaningful element gets `data-wave-id` (`n_` + 4 or more
lowercase letters or digits); `wave_assign_ids` adds missing ones.
3. **From FEATURE.md**, on the elements themselves:
- each input `data-wave-field="<path>"` (its rules, options and default
come from the brief; write them too if you like);
- each piece of data `data-wave-content="dynamic"` and
`data-wave-bind="<path>"`;
- each action's control `data-wave-action="action/<id>"`, and
`data-wave-trigger`, `data-wave-effect`, `data-wave-to`,
`data-wave-to-failure` as the brief says;
- the screen's meta: `wave:screen`, `wave:flow`, `wave:route`,
`wave:title`.
4. **Draw every state the brief implies**: an error message for each rule
(`data-wave-state-of` the field, `data-wave-state="error"`), a warning
where the brief has one, the loading state of every action with effects,
the screen's loading and error states when it shows data, and every
dialog an action confirms with.
5. **Choices are radios or checkboxes**: chips and cards to pick from are a
`role="radiogroup"` (or group) of `role="radio"`/`"checkbox"`
buttons with `aria-checked`, the group carrying `data-wave-field`.
6. **Tokens only**: every colour, size and font is `var(--token)`.
7. **One self-contained file** per screen (see Fidelity), with the viewport tag.
Then **Wave Review** reads the screens and asks the few questions left.
## Fidelity: the upload must look exactly like the design
- **Export, never regenerate.** Start from the exact HTML the designer saw.
Every change you make is additive (ids, attributes, asset addresses, token
variables for the same values) and listed for the designer.
- **One self-contained file.** All CSS in `<style>` in the page. No local
scripts or stylesheets; nothing but the HTML file is uploaded.
- **Scripts run, but in a sandbox with no storage.** `localStorage`,
`sessionStorage`, `indexedDB` and cookies throw there: remove such code
or wrap it in try/catch. A page built by a script at run time (an empty
`<div id="root">` filled by JavaScript) cannot be specified: export the
rendered HTML instead.
- **Viewport.** Include `<meta name="viewport" content="width=device-width, initial-scale=1">`.
| Looks different in Post-it | Usually because | Fix |
| --- | --- | --- |
| Fonts are wrong | A font file was local or inline | `upload_asset` the font, point `@font-face` at the hosted address |
| Images missing | Local or relative paths | `upload_asset` each, use the hosted addresses |
| Part of the page missing | A script failed (storage, a missing file) | Remove or guard the script; export rendered HTML |
| Everything missing | The page is built by a script | Export the rendered DOM as HTML |
| Colours or sizes shifted | Values changed while tokenising | Use the token with the same value; add a token if none |
| Layout wrong at a width | Viewport tag missing, or designed for another width | Add the viewport tag; check `wave:viewports` |
| Interactions dead | Handlers used storage or missing files | As above |
## Screen meta, in the head
```html
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="wave:spec" content="1">
<meta name="wave:screen" content="checkout-address">
<meta name="wave:flow" content="checkout">
<meta name="wave:route" content="/checkout/:orderId/address">
<meta name="wave:title" content="Add delivery address">
<meta name="wave:access" content="signed-in">
<meta name="wave:viewports" content="375 768 1440">
<script type="application/wave+json" id="wave-resources">
{ "user/firstName": { "type": "string", "source": "auth profile", "description": "Given name" } }
</script>
```
## Attributes
| Attribute | Meaning | Example |
| --- | --- | --- |
| `data-wave-id` | Stable, opaque, designer-owned id. Never changed or reused. | `n_7f3a2c` |
| `data-wave-slug` | Human name, unique within the screen. Renaming it breaks nothing. | `add-address-button` |
| `data-wave-component` | The design-system component this should become. | `Button` |
| `data-wave-variant` | The component variant. | `primary` |
| `data-wave-ds` | The design-system id of the component variant this is (from the catalogue). | `DS.primaryButton` |
| `data-wave-tag` | The tag Figma drew this as, before the semantic upgrade made it a real element (wave-figma). | `div` |
| `data-wave-from` | On an input the semantic upgrade made: the tag it replaced (wave-figma). | `p` |
| `data-wave-text` | On an input the semantic upgrade made: whether the drawn text became its placeholder or value. | `placeholder` |
| `data-wave-filled-color` | On an input the semantic upgrade made: the colour of typed text. | `var(--color-text-primary)` |
| `data-wave-insert` | A native checkbox or radio the semantic upgrade added inside a drawn control; hidden, it carries the choice. | |
| `data-wave-role` | Semantic role where the tag does not say it (for example input). | `form` |
| `data-wave-origin` | Set to wave on ids the review tool created. Remove it once adopted. | `wave` |
| `data-wave-content` | static or dynamic. | `dynamic` |
| `data-wave-bind` | Resource path the content comes from. Free text, path grammar. | `user/firstName` |
| `data-wave-sample` | Example value. Defaults to the rendered text. | `Asim` |
| `data-wave-empty` | What to show when the value is missing. | `there` |
| `data-wave-format` | How the value is formatted. Free text. | `currency:GBP` |
| `data-wave-max` | Maximum length before truncation. | `40` |
| `data-wave-repeat` | This element repeats over a list resource. | `orders[]` |
| `data-wave-item` | Marks the child that is the repeated item template (no value). | |
| `data-wave-action` | Action name. Free text, path grammar. | `action/signup/add-address` |
| `data-wave-trigger` | click, submit, change or load. Defaults to click. | `click` |
| `data-wave-effect` | Named side effects, space separated. Free text. | `api/address/create` |
| `data-wave-to` | Destination on success: screen:, node:, modal:, back, url:. | `screen:checkout-review` |
| `data-wave-to-failure` | Destination or node revealed on failure. | `node:checkout-address/form-error` |
| `data-wave-field` | The field this control writes. Free text, path grammar. | `address/postcode` |
| `data-wave-validate` | Validation rules, separated by semicolons. | `required; pattern:uk-postcode; max:8` |
| `data-wave-states` | States this node supports, space separated. | `default hover disabled loading error` |
| `data-wave-state` | Which state this element depicts. | `error` |
| `data-wave-state-of` | This element depicts another node (by id) in the state named by data-wave-state. | `n_7f3a2c` |
| `data-wave-visible-if` | Visibility condition over resource paths. Free text. | `user/isLoggedIn` |
| `data-wave-access` | Who can see it, when it differs from the screen: public, signed-in, role:<name>, plan:<name>. | `role:admin` |
| `data-wave-flag` | Feature flag it is behind. | `flags/new-checkout` |
| `data-wave-responsive` | What happens on small screens: stack, hide, collapse, scroll, or a note. | `stack` |
| `data-wave-behavior` | Behaviours on top of the element type, space separated: carousel, reorderable, draggable, drop-target, accordion, collapsible, infinite-scroll, swipe-actions, sticky, pull-to-refresh, copy-to-clipboard. | `carousel` |
| `data-wave-config` | Settings a behaviour needs, key:value separated by semicolons. | `autoplay:off; loop:on; controls:arrows dots` |
| `data-wave-icon` | Icon name from the icon library. | `lucide:search` |
| `data-wave-waived` | Answers the designer decided not to give, as JSON {field: reason}. | `{"to-failure":"Navigation only, cannot fail"}` |
| `data-wave-copy` | For static text: final, draft or placeholder. | `final` |
| `data-wave-copy-source` | Where static copy will live: code, cms, or i18n:<key>. | `i18n:checkout.address.title` |
| `data-wave-overflow` | When text is too long: wrap, truncate, or clamp:<lines>. | `clamp:2` |
| `data-wave-fit` | How an image fills its box: cover, contain or fill, optionally with a ratio. | `cover 16:9` |
| `data-wave-asset` | Where the real asset will live: cdn, bundled or user-upload. | `cdn` |
| `data-wave-values` | Every value a status can take and how each looks, value:variant separated by spaces. | `paid:success pending:warning failed:danger` |
| `data-wave-sort` | The order of a list or the sortable column. | `newest` |
| `data-wave-paginate` | How much of a list shows: all, pages:<n>, load-more:<n> or infinite:<n>. | `pages:20` |
| `data-wave-empty-state` | The node shown when a list has nothing, by id or slug. | `no-orders` |
| `data-wave-filter` | What filters a list and on what. | `status-tabs:order/status` |
| `data-wave-playback` | Media playback: autoplay, muted, loop, controls. | `controls muted` |
| `data-wave-disabled-if` | When the control cannot be used. | `form/invalid` |
| `data-wave-confirm` | The dialog that asks for confirmation first, by id or slug, or none. | `confirm-delete` |
| `data-wave-feedback` | The toast or banner shown on success, by id or slug, or none. | `saved-toast` |
| `data-wave-shortcut` | Keyboard shortcut. | `mod+s` |
| `data-wave-dismiss` | How a dialog, toast or banner goes away: close-button, backdrop, escape, auto:<seconds>, choice. | `close-button escape` |
| `data-wave-active-if` | When a navigation item is the current one. | `route/section == orders` |
| `data-wave-controls` | The panel a tab shows, by id or slug. | `orders-panel` |
| `data-wave-commit` | Whether a switch or checkbox acts immediately or on save: instant or save. | `instant` |
| `data-wave-track` | Analytics event sent. | `checkout_address_saved` |
| `data-wave-options` | Choices: a list separated by \|, or a resource path. | `catalog/sizes[]` |
| `data-wave-default` | The starting value, or none. | `none` |
| `data-wave-validate-on` | When a form shows errors: submit, blur or change. | `blur` |
| `data-wave-dirty-guard` | Whether leaving with unsaved changes warns: on or off. | `on` |
Names (resources, actions, effects, fields) are free text in a path grammar;
reuse the same name for the same thing across screens. `none` is a valid
answer where nothing applies; what Wave refuses is no answer at all.
## What is never asked
Every element inherits, in this order, and anything inherited is not a
question:
1. **Its own HTML** (`data-wave-*`, native attributes).
2. **FEATURE.md**: the fields it writes (`data-wave-field`: rules, options,
default, shown-when), the data it shows (`data-wave-bind`: type, source,
empty, format), the action it takes (trigger, effects, destinations,
confirm, tracking) and the screen's route, title and access.
3. **Its component** in the catalogue: states, variants, responsive
behaviour, events; parts inside a component instance belong to it.
4. **DESIGN.md**: copy status and where copy lives, who can see things,
analytics, flags, form behaviour, empty values, overflow, icons,
viewports, language.
5. **What Wave knows for certain**: names (slugs), element types, a submit
button's trigger, the action name, "none" for a control that only
navigates.
When answers are applied, what came from FEATURE.md and what Wave is certain
of is written into the HTML; DESIGN.md and the catalogue stay the policy.
Wave Review
For Claude Design: ask only the questions left, check, upload, make the prototype and handle review.
---
name: Wave Review
description: Analyse a feature's HTML mockups against DESIGN.md, FEATURE.md and the catalogue, ask only the questions still open (grouped, with proposals), run the Wave dry run for product, check, upload to Post-it, make the clickable prototype and handle review comments.
---
# Wave Review
The screens exist (from Wave Feature, or brought by the designer). Wave now
works out everything it can from the HTML, FEATURE.md, the catalogue and
DESIGN.md, and what is left are the real decisions.
## Steps
1. **Context.** `wave_get_brief` (design and feature) and `get_catalogue`.
A mockup made without Wave Feature: write FEATURE.md from it first (Wave
Feature, part 1: read the screens and the prompt, propose the fields,
data and actions, confirm), because every answer in the brief answers
every element that uses it.
2. **Ids.** `wave_assign_ids` on each screen (it never changes an id).
3. **Analyse.** `wave_dry_run` with the feature and every screen. It saves
the question sheet ("Wave questions") with only what is open: one entry
per decision, listing every element it applies to, mandatory first, split
into questions for product and for the designer, each with Wave's
proposal. What was inherited is counted, not listed.
4. **Before asking, look for a better home for the answer.** A question
about a field, data or an action belongs in FEATURE.md; a question that
will repeat on every screen (copy, analytics, access) belongs in
DESIGN.md; a question about a component's states belongs in its
specimen. Update the brief (with the designer's agreement) and run again:
it answers every element at once.
5. **Ask the designer the rest**, a group at a time, with the proposal:
"These 3 step buttons are disabled: when can people jump to a step?".
Write their answers in the sheet (one answer under a grouped question
covers every element in it) and run `wave_dry_run` again. Product's
questions: give the designer the link to "Wave questions" to share; when
product has answered in it, run again. It marks answers to fix
("**Fix:** ..."); tidy plain words into the format asked. Repeat until it
**passes** (it saves "Wave answers"). "Wave dry run" stops here: nothing
is uploaded.
6. **Apply.** `wave_apply_answers` with the "Wave answers" sheet on each
screen: it writes the answers, the brief's values and the names Wave
assigned into the HTML.
7. **Assets.** Every local or inline image, SVG file, icon, logo and font:
`upload_asset` (project id, file name, base64 bytes), then use the
returned address. Links to other websites stay.
8. **Preflight.** `preflight_html` (target = the feature) on every screen;
fix everything mandatory. Then **show the designer** each screen, what you
changed (ids, attributes, asset addresses, tokens), what they confirmed,
anything waived and the result. **Ask: "Does this match what you
designed? May I upload it?"** Upload only on a clear yes.
- Publish each screen into the feature folder with `attach_file` (`<screen-slug>.html`)
or `create_page` (`content_type: "html"`, `parent_id` = the feature). For a new
version, `read_page` then `update_page` with its version. Specimens go into
`design-system/components` the same way.
- After saving, `check_screen` shows what Wave still finds missing on the uploaded file.
9. **Compare after upload.** Give the designer the Post-it link to each
uploaded screen (https://<post-it>/review/<page id>) to compare with the original side by
side; fix any difference (see Fidelity in Wave Feature), preflight and
upload again.
10. **Prototype** (below), then give the designer the prototype link.
11. Only then ask for review.
A waiver (`waive: <reason>`) is the designer's call, for something that
really does not apply. Optional questions are hidden; ask for "the optional
questions" only if the designer wants them.
## Prototype: the feature as a working product, on a mock API
A feature plays as one prototype: every screen in one frame with a device bar
(mobile, tablet, desktop), links and actions moving between screens, forms
validating, and the data coming from a **mock API** served by MSW in the
page. The mock API is the feature's OpenAPI document; the screens connect to
it through the attributes they already have.
1. **Publish the whole flow at once** when there are several screens:
`wave_publish_flow` (feature id, every screen's name and HTML, and the
OpenAPI document and mock files if you have them). It preflights and saves
every screen, then the API, and returns the review and prototype links.
The designer's confirmation (step 8 above) still comes first.
2. **The API.** If the designer or product gave an OpenAPI file, use it.
Otherwise `wave_generate_api` drafts one from the uploaded screens: one
GET per data root the screens read, one POST per `api/...` effect, with
the values the design shows as examples. Show the designer the draft and
the data requirements it lists, and improve it with them:
- realistic examples: more list items, long and short values, an empty list;
- every failure product expects (validation 422, not found 404, server 500),
as extra responses or named examples: each becomes a choice in the
prototype's **Scenarios** menu;
- `x-wave-delay` on slow calls, so loading states show.
Save it with `wave_save_api` (JSON or YAML; mock files are response
bodies by operationId). It rewrites the feature's **Data requirements** page
and lists anything the screens read or call that the API does not serve.
3. **How screens meet the API** (keep these exact):
- `x-wave-provides: order` on a GET: its response is the data root
`order`, so `data-wave-bind="order/total"`, `data-wave-repeat="order/items[]"`
and `data-wave-visible-if="order/paymentFailed"` read it.
- `x-wave-effect: api/orders/place` on an operation: an action with
`data-wave-effect="api/orders/place"` calls it, shows the loading state
drawn for the control, then follows `data-wave-to` on success or
`data-wave-to-failure` on an error response.
- Form fields (`data-wave-field`) are validated (`data-wave-validate`)
on submit, showing the error states drawn for them, and sent as the body.
- Route parameters (`:orderId`) come from the path parameters' examples.
4. `get_prototype` gives the link and what is still missing. Give the
designer the link: "Click through it on mobile and desktop, and try the
failure scenarios." Fix what they find.
5. To show it to somebody without a Post-it account (a client, a stakeholder),
`share_prototype` makes a link (label it for who it is for; give an
expiry). Give the designer the link at once: it is not shown again.
Screens that call `fetch` themselves also work: every request to the API's
base address is answered by the same mock server. Use `fetch`, never
`XMLHttpRequest` or jQuery (preflight warns about both).
An action with `data-wave-confirm="<dialog slug>"` opens that dialog first
in the prototype: its cancel control (`data-wave-to="back"`, or a button
reading Cancel, No or Keep) closes it; its other button confirms and runs the
action. So draw the dialog on the screen, with both buttons.
## Review rounds
1. `list_comments` with the feature's `flow_id` and `status: "open"`.
2. `read_page` each affected screen, make the changes (keeping ids), preflight, show the
designer, and `update_page`. Note the version each save returns.
3. For each comment you dealt with, `mark_addressed` with its `comment_id`, the version
that fixes it and one sentence on what changed.
4. When every screen and the flow are complete, `ask_for_review` on each screen.
Each comment says where it points: an address (`screen.type.slug`) and
`data-wave-id`, quoted words, an area, or an element without an id (give it
one). You cannot resolve comments: a reviewer confirms. If you disagree, say so
to the designer rather than marking it addressed.
## Rules that matter most
1. Every meaningful element has a `data-wave-id` (`n_` + at least 4
lowercase letters or digits). **Never change or reuse an id**: comments and
answers are anchored to ids.
2. Every element's address is `screen.type.slug`; its parent's address is the
same for the element it sits in. Slugs are unique on a screen; screen slugs
are unique in the project.
3. Always read the latest version before editing: reviewers' confirmed values
and waivers are saved as new versions of the file.
4. Only the uploader can change a screen in Post-it; everybody else comments.
## What is never asked
Every element inherits, in this order, and anything inherited is not a
question:
1. **Its own HTML** (`data-wave-*`, native attributes).
2. **FEATURE.md**: the fields it writes (`data-wave-field`: rules, options,
default, shown-when), the data it shows (`data-wave-bind`: type, source,
empty, format), the action it takes (trigger, effects, destinations,
confirm, tracking) and the screen's route, title and access.
3. **Its component** in the catalogue: states, variants, responsive
behaviour, events; parts inside a component instance belong to it.
4. **DESIGN.md**: copy status and where copy lives, who can see things,
analytics, flags, form behaviour, empty values, overflow, icons,
viewports, language.
5. **What Wave knows for certain**: names (slugs), element types, a submit
button's trigger, the action name, "none" for a control that only
navigates.
When answers are applied, what came from FEATURE.md and what Wave is certain
of is written into the HTML; DESIGN.md and the catalogue stay the policy.
## The decision tree: what to ask for every element
Wave detects each element's type. For each type, ask every **mandatory**
question (and the recommended ones the designer wants to answer). Questions
marked "designer" are about look, components, states and accessibility;
"product" ones are about data, behaviour, rules, navigation, permissions and
tracking (the designer answers these too, having agreed them with product).
### Every element
| Field | Level | Asked of | Written as | Question |
| --- | --- | --- | --- | --- |
| Element type | worked out by Wave | | `data-wave-role` to override | Never asked: Wave detects it. |
| Name (slug) | worked out by Wave | | `data-wave-slug` to override | Never asked: named from its field, action, component or text, unique on the screen. |
| Shown when (when it is hidden in the mockup) | mandatory when hidden, else recommended | product | `data-wave-visible-if` | This is hidden in the mockup. When is it shown? |
### The screen
| Field | Level | Asked of | Written as | Question |
| --- | --- | --- | --- | --- |
| Screen slug | **mandatory** | product | `wave:screen` | What is this screen called? A slug unique in the project. |
| Title | **mandatory** | product | `wave:title` | The page title? |
| Route | **mandatory** | product | `wave:route` | What URL is it at, with parameters? e.g. /checkout/:orderId/address |
| Feature | **mandatory** | product | `wave:flow` | Which feature (flow) is this part of? |
| Who can open it | **mandatory** | product | `wave:access` | Who can open this: public, signed-in, role:<name>? |
| Designed for widths | **mandatory** | designer | `wave:viewports` | Which widths is it designed for? e.g. 375 768 1440 |
| How people arrive | recommended | product | `wave:entry` | How do people arrive here: link, email, push, another screen? |
| Page-view event | recommended | product | `wave:track` | What is the page-view analytics event, or none? |
| Language | recommended | designer | `lang` | Which language is the page in (html lang)? |
| Viewport tag | **mandatory** | designer | the design | Add <meta name="viewport" content="width=device-width, initial-scale=1">. |
| Screen loading state (if the screen shows data) | **mandatory** | designer | the design | The screen shows data. Draw what it looks like while loading (an element with data-wave-state="loading"). |
| Screen error state (if the screen shows data) | **mandatory** | designer | the design | The screen shows data. Draw what it looks like if loading fails (an element with data-wave-state="error"). |
| Data: each path (bind, repeat, field) | **mandatory** | product | wave-resources | What is it? Its type, where it comes from, and what it means (type; source; description). |
### Structure
#### section
| Field | Level | Asked of | Written as | Question |
| --- | --- | --- | --- | --- |
| Who can see it | recommended | product | `data-wave-access` | Can everyone on this screen see it, or only some roles or plans? |
| On small screens | recommended | designer | `data-wave-responsive` | What happens to this on mobile: stack, hide, collapse, scroll? |
| Feature flag | recommended | product | `data-wave-flag` | Is this behind a feature flag? Which one, or none? |
#### card
| Field | Level | Asked of | Written as | Question |
| --- | --- | --- | --- | --- |
| Component | **mandatory** | designer | `data-wave-component` | Which design-system component is this? |
| Variant | **mandatory** | designer | `data-wave-variant` | Which variant: primary, secondary, tertiary, destructive, or another the system has? |
| Who can see it | recommended | product | `data-wave-access` | Can everyone on this screen see it, or only some roles or plans? |
| On small screens | recommended | designer | `data-wave-responsive` | What happens to this on mobile: stack, hide, collapse, scroll? |
#### list
| Field | Level | Asked of | Written as | Question |
| --- | --- | --- | --- | --- |
| List of | **mandatory** | product | `data-wave-repeat` | What is this a list of? A data path ending in [], e.g. orders[]. |
| Item template | **mandatory** | designer | the design | Mark the one child that is the item template (data-wave-item). Other copies are samples. |
| Order | **mandatory** | product | `data-wave-sort` | In what order: newest, price, alphabetical, as returned? |
| How many | **mandatory** | product | `data-wave-paginate` | All at once, pages, load more, or infinite scroll? all, pages:20, load-more:20, infinite:20. |
| When empty | **mandatory** | designer | `data-wave-empty-state` | What shows when there are none? The empty state's slug. |
| Filters | recommended | product | `data-wave-filter` | Which controls filter it, and on what? |
| Who can see it | recommended | product | `data-wave-access` | Can everyone on this screen see it, or only some roles or plans? |
| On small screens | recommended | designer | `data-wave-responsive` | What happens to this on mobile: stack, hide, collapse, scroll? |
#### table
| Field | Level | Asked of | Written as | Question |
| --- | --- | --- | --- | --- |
| List of | **mandatory** | product | `data-wave-repeat` | What is this a list of? A data path ending in [], e.g. orders[]. |
| Item template | **mandatory** | designer | the design | Mark the one child that is the item template (data-wave-item). Other copies are samples. |
| Order | **mandatory** | product | `data-wave-sort` | In what order: newest, price, alphabetical, as returned? |
| How many | **mandatory** | product | `data-wave-paginate` | All at once, pages, load more, or infinite scroll? all, pages:20, load-more:20, infinite:20. |
| When empty | **mandatory** | designer | `data-wave-empty-state` | What shows when there are none? The empty state's slug. |
| Filters | recommended | product | `data-wave-filter` | Which controls filter it, and on what? |
| Who can see it | recommended | product | `data-wave-access` | Can everyone on this screen see it, or only some roles or plans? |
| On small screens | recommended | designer | `data-wave-responsive` | What happens to this on mobile: stack, hide, collapse, scroll? |
#### modal
| Field | Level | Asked of | Written as | Question |
| --- | --- | --- | --- | --- |
| Kind | **mandatory** | designer | `data-wave-role` | Modal, drawer, sheet or full screen? |
| Opened by | **mandatory** | product | the design | Nothing opens this. Give the control that opens it data-wave-to="modal:<screen>/<this slug>". |
| Closes by | **mandatory** | product | `data-wave-dismiss` | How does it close: close-button, backdrop, escape, or only by choosing? |
| Component | **mandatory** | designer | `data-wave-component` | Which design-system component is this? |
#### navigation
| Field | Level | Asked of | Written as | Question |
| --- | --- | --- | --- | --- |
| Current item | **mandatory** | product | `data-wave-active-if` | How do we know which item is current? |
| Every item goes somewhere | **mandatory** | product | the design | Every item needs a destination (data-wave-to). |
| Component | **mandatory** | designer | `data-wave-component` | Which design-system component is this? |
| On small screens | recommended | designer | `data-wave-responsive` | What happens to this on mobile: stack, hide, collapse, scroll? |
#### menu
| Field | Level | Asked of | Written as | Question |
| --- | --- | --- | --- | --- |
| Opens on | **mandatory** | product | `data-wave-trigger` | What opens it: click or hover? |
| Component | **mandatory** | designer | `data-wave-component` | Which design-system component is this? |
### Content
#### heading
| Field | Level | Asked of | Written as | Question |
| --- | --- | --- | --- | --- |
| Fixed or from data | **mandatory** | product | `data-wave-content` | Is this text fixed, or does it come from data? |
| Copy status (if fixed text) | **mandatory** | product | `data-wave-copy` | Is this the final copy? final, draft or placeholder. |
| Where the copy lives (if fixed text) | recommended | product | `data-wave-copy-source` | Where will this copy live: code, cms, or a translation key (i18n:<key>)? |
| Data (if from data) | **mandatory** | product | `data-wave-bind` | Which data does it show? A path such as user/firstName. |
| When empty (if from data) | **mandatory** | product | `data-wave-empty` | What shows if there is no value: hide it, a dash, or some text? |
| Overflow (if from data) | **mandatory** | designer | `data-wave-overflow` | If it is too long: wrap, truncate, or clamp to N lines (clamp:2)? |
| Example value (if from data) | recommended | product | `data-wave-sample` | Is the text shown a realistic example? |
| Max length (if from data) | recommended | product | `data-wave-max` | How long can it get? |
#### text
| Field | Level | Asked of | Written as | Question |
| --- | --- | --- | --- | --- |
| Fixed or from data | **mandatory** | product | `data-wave-content` | Is this text fixed, or does it come from data? |
| Copy status (if fixed text) | **mandatory** | product | `data-wave-copy` | Is this the final copy? final, draft or placeholder. |
| Where the copy lives (if fixed text) | recommended | product | `data-wave-copy-source` | Where will this copy live: code, cms, or a translation key (i18n:<key>)? |
| Data (if from data) | **mandatory** | product | `data-wave-bind` | Which data does it show? A path such as user/firstName. |
| When empty (if from data) | **mandatory** | product | `data-wave-empty` | What shows if there is no value: hide it, a dash, or some text? |
| Overflow (if from data) | **mandatory** | designer | `data-wave-overflow` | If it is too long: wrap, truncate, or clamp to N lines (clamp:2)? |
| Example value (if from data) | recommended | product | `data-wave-sample` | Is the text shown a realistic example? |
| Max length (if from data) | recommended | product | `data-wave-max` | How long can it get? |
#### label
| Field | Level | Asked of | Written as | Question |
| --- | --- | --- | --- | --- |
| Fixed or from data | **mandatory** | product | `data-wave-content` | Is this text fixed, or does it come from data? |
| Copy status (if fixed text) | **mandatory** | product | `data-wave-copy` | Is this the final copy? final, draft or placeholder. |
| Where the copy lives (if fixed text) | recommended | product | `data-wave-copy-source` | Where will this copy live: code, cms, or a translation key (i18n:<key>)? |
| Data (if from data) | **mandatory** | product | `data-wave-bind` | Which data does it show? A path such as user/firstName. |
| When empty (if from data) | **mandatory** | product | `data-wave-empty` | What shows if there is no value: hide it, a dash, or some text? |
| Overflow (if from data) | **mandatory** | designer | `data-wave-overflow` | If it is too long: wrap, truncate, or clamp to N lines (clamp:2)? |
| Example value (if from data) | recommended | product | `data-wave-sample` | Is the text shown a realistic example? |
| Max length (if from data) | recommended | product | `data-wave-max` | How long can it get? |
#### inlineValue
| Field | Level | Asked of | Written as | Question |
| --- | --- | --- | --- | --- |
| Fixed or from data | **mandatory** | product | `data-wave-content` | Is this text fixed, or does it come from data? |
| Copy status (if fixed text) | **mandatory** | product | `data-wave-copy` | Is this the final copy? final, draft or placeholder. |
| Where the copy lives (if fixed text) | recommended | product | `data-wave-copy-source` | Where will this copy live: code, cms, or a translation key (i18n:<key>)? |
| Data (if from data) | **mandatory** | product | `data-wave-bind` | Which data does it show? A path such as user/firstName. |
| When empty (if from data) | **mandatory** | product | `data-wave-empty` | What shows if there is no value: hide it, a dash, or some text? |
| Overflow (if from data) | **mandatory** | designer | `data-wave-overflow` | If it is too long: wrap, truncate, or clamp to N lines (clamp:2)? |
| Example value (if from data) | recommended | product | `data-wave-sample` | Is the text shown a realistic example? |
| Max length (if from data) | recommended | product | `data-wave-max` | How long can it get? |
| Format (for values and dynamic text) | recommended | product | `data-wave-format` | How is it formatted? e.g. currency:GBP, date:relative, number:0dp, percent:1dp. |
#### formattedValue
| Field | Level | Asked of | Written as | Question |
| --- | --- | --- | --- | --- |
| Fixed or from data | **mandatory** | product | `data-wave-content` | Is this text fixed, or does it come from data? |
| Copy status (if fixed text) | **mandatory** | product | `data-wave-copy` | Is this the final copy? final, draft or placeholder. |
| Where the copy lives (if fixed text) | recommended | product | `data-wave-copy-source` | Where will this copy live: code, cms, or a translation key (i18n:<key>)? |
| Data (if from data) | **mandatory** | product | `data-wave-bind` | Which data does it show? A path such as user/firstName. |
| When empty (if from data) | **mandatory** | product | `data-wave-empty` | What shows if there is no value: hide it, a dash, or some text? |
| Overflow (if from data) | **mandatory** | designer | `data-wave-overflow` | If it is too long: wrap, truncate, or clamp to N lines (clamp:2)? |
| Example value (if from data) | recommended | product | `data-wave-sample` | Is the text shown a realistic example? |
| Max length (if from data) | recommended | product | `data-wave-max` | How long can it get? |
| Format (for values and dynamic text) | **mandatory** | product | `data-wave-format` | How is it formatted? e.g. currency:GBP, date:relative, number:0dp, percent:1dp. |
#### image
| Field | Level | Asked of | Written as | Question |
| --- | --- | --- | --- | --- |
| Alt text | **mandatory** | designer | `alt` | Describe it for screen readers, or leave alt empty if it is decorative. |
| Fixed or from data | **mandatory** | product | `data-wave-content` | Is it always this image, or from data? |
| Data (if from data) | **mandatory** | product | `data-wave-bind` | Which data gives the image? |
| Fallback (if from data) | **mandatory** | designer | `data-wave-empty` | What shows if there is no image or it fails to load? |
| Fit | **mandatory** | designer | `data-wave-fit` | Crop to fill or fit inside, and what ratio? e.g. cover 16:9. |
| Where it lives | recommended | product | `data-wave-asset` | Where will the real image live: cdn, bundled, user-upload? |
#### icon
| Field | Level | Asked of | Written as | Question |
| --- | --- | --- | --- | --- |
| Decorative or meaningful | **mandatory** | designer | `aria-label` | Does it mean something on its own? If yes give it an aria-label; if not, aria-hidden="true". |
| Icon name | **mandatory** | designer | `data-wave-icon` | Which icon from the library? e.g. lucide:search. |
#### avatar
| Field | Level | Asked of | Written as | Question |
| --- | --- | --- | --- | --- |
| Alt text | **mandatory** | designer | `alt` | Describe it for screen readers, or leave alt empty if it is decorative. |
| Fixed or from data | **mandatory** | product | `data-wave-content` | Is it always this image, or from data? |
| Data (if from data) | **mandatory** | product | `data-wave-bind` | Which data gives the image? |
| Fallback (if from data) | **mandatory** | designer | `data-wave-empty` | What shows if there is no image or it fails to load? |
| Fit | **mandatory** | designer | `data-wave-fit` | Crop to fill or fit inside, and what ratio? e.g. cover 16:9. |
| Where it lives | recommended | product | `data-wave-asset` | Where will the real image live: cdn, bundled, user-upload? |
| Component | **mandatory** | designer | `data-wave-component` | Which design-system component is this? |
#### badge
| Field | Level | Asked of | Written as | Question |
| --- | --- | --- | --- | --- |
| Fixed or from data | **mandatory** | product | `data-wave-content` | Is this text fixed, or does it come from data? |
| Data (if from data) | **mandatory** | product | `data-wave-bind` | Which data does it show? A path such as user/firstName. |
| Values and looks (if from data) | **mandatory** | product | `data-wave-values` | Every value it can take and how each looks, e.g. paid:success pending:warning failed:danger. |
| Component | **mandatory** | designer | `data-wave-component` | Which design-system component is this? |
| Variant | recommended | designer | `data-wave-variant` | Which variant: primary, secondary, tertiary, destructive, or another the system has? |
#### media
| Field | Level | Asked of | Written as | Question |
| --- | --- | --- | --- | --- |
| Fixed or from data | **mandatory** | product | `data-wave-content` | Always this media, or from data? |
| Data (if from data) | **mandatory** | product | `data-wave-bind` | Which data does it show? A path such as user/firstName. |
| Playback | **mandatory** | designer | `data-wave-playback` | Autoplay, muted, loop, controls? |
#### chart
| Field | Level | Asked of | Written as | Question |
| --- | --- | --- | --- | --- |
| Data (if from data) | **mandatory** | product | `data-wave-bind` | Which data, over what period? |
| Component | **mandatory** | designer | `data-wave-component` | Which design-system component is this? |
| When empty (if from data) | **mandatory** | product | `data-wave-empty` | What shows with no data? |
#### map
| Field | Level | Asked of | Written as | Question |
| --- | --- | --- | --- | --- |
| Data (if from data) | **mandatory** | product | `data-wave-bind` | Which location data and markers does it show? |
| When empty (if from data) | **mandatory** | product | `data-wave-empty` | What shows with no locations? |
| Component | **mandatory** | designer | `data-wave-component` | Which design-system component is this? |
| Interactions | **mandatory** | product | `data-wave-config` | What can people do on the map: pan, zoom, select a marker (and what that does)? |
### Actions
#### button
| Field | Level | Asked of | Written as | Question |
| --- | --- | --- | --- | --- |
| Label | **mandatory** | designer | `aria-label` | An icon-only button needs an aria-label saying what it does. |
| Component | **mandatory** | designer | `data-wave-component` | Which design-system component is this? |
| Variant | **mandatory** | designer | `data-wave-variant` | Which variant: primary, secondary, tertiary, destructive, or another the system has? |
| Action | **mandatory** | product | `data-wave-action` | What is this action called? e.g. action/checkout/add-address. |
| Trigger | **mandatory** | product | `data-wave-trigger` | What triggers it: click, submit, change or load? |
| Side effects | **mandatory** | product | `data-wave-effect` | What happens behind it: API calls, analytics, emails, payments? Space separated, or none. |
| On success | **mandatory** | product | `data-wave-to` | Where does the user end up when it works? screen:, node:, modal:, back, url:, or stay. |
| On failure (if it has side effects) | **mandatory** | product | `data-wave-to-failure` | What happens if it fails? The node that shows the error (node:screen/slug), a screen, or none. |
| States | **mandatory** | designer | `data-wave-states` | Which states does it have? e.g. default hover focus disabled loading error. |
| Loading state drawn (if it has side effects) | **mandatory** | designer | the design | It does work behind the scenes, so draw its loading state (data-wave-state-of here, data-wave-state="loading"). |
| Disabled when (if it can be disabled) | **mandatory** | product | `data-wave-disabled-if` | When can it not be pressed? |
| Asks to confirm (if it looks destructive (delete, remove, cancel…)) | **mandatory** | product | `data-wave-confirm` | This looks destructive. Does it ask "are you sure" first? The dialog's slug, or none. |
| Success message (if it has side effects) | recommended | product | `data-wave-feedback` | Is there a message when it works? The toast's slug, or none. |
| Analytics event | recommended | product | `data-wave-track` | Is using this tracked? Event name, or none. |
#### link
| Field | Level | Asked of | Written as | Question |
| --- | --- | --- | --- | --- |
| On success | **mandatory** | product | `data-wave-to` | Where does it go? screen:<slug>, url:https://…, node:, modal:, back. |
| New tab (if it goes to another website) | **mandatory** | product | `target` | It leaves the product. Open in a new tab (_blank) or the same one (_self)? |
| Analytics event | recommended | product | `data-wave-track` | Is using this tracked? Event name, or none. |
### Inputs
#### form
| Field | Level | Asked of | Written as | Question |
| --- | --- | --- | --- | --- |
| Submit button | **mandatory** | product | the design | Which button submits it? Give one button data-wave-trigger="submit". |
| Shows errors on | **mandatory** | product | `data-wave-validate-on` | When are errors shown: submit, blur (leaving a field) or change (as they type)? |
| Warn on leaving | recommended | product | `data-wave-dirty-guard` | Warn if they leave with unsaved changes? on or off. |
#### textInput
| Field | Level | Asked of | Written as | Question |
| --- | --- | --- | --- | --- |
| Writes to | **mandatory** | product | `data-wave-field` | Which data does this set? A path such as address/postcode. |
| Input type | **mandatory** | designer | `type` | Which input type: text, email, password, number, tel, url, search? |
| Label | **mandatory** | designer | the design | It needs a label people and screen readers can read. |
| Required and rules | **mandatory** | product | `data-wave-validate` | Must it be filled in, and what rules apply (format, length, range)? Say optional if none. |
| Error message drawn (if it has validation rules) | **mandatory** | designer | the design | Draw the error message for each rule (an element with data-wave-state-of pointing here and data-wave-state="error"). |
| States | **mandatory** | designer | `data-wave-states` | Which states does it have? e.g. default hover focus disabled loading error. |
| Component | **mandatory** | designer | `data-wave-component` | Which design-system component is this? |
| Input mask (for text and phone inputs) | recommended | product | `data-wave-format` | Is it auto-formatted as they type (phone, card, date)? |
| Autocomplete | recommended | designer | `autocomplete` | Browser autocomplete hint (e.g. email, postal-code, cc-number)? |
#### select
| Field | Level | Asked of | Written as | Question |
| --- | --- | --- | --- | --- |
| Writes to | **mandatory** | product | `data-wave-field` | Which data does this set? A path such as address/postcode. |
| Label | **mandatory** | designer | the design | It needs a label people and screen readers can read. |
| Options | **mandatory** | product | `data-wave-options` | What are the choices, and where do they come from? A list separated by \|, or a data path. |
| Starting value | **mandatory** | product | `data-wave-default` | What is it set to at the start? A value, or none. |
| Required and rules | **mandatory** | product | `data-wave-validate` | Must a choice be made? required or optional. |
| Error message drawn (if it has validation rules) | **mandatory** | designer | the design | Draw the error message for each rule (an element with data-wave-state-of pointing here and data-wave-state="error"). |
| States | **mandatory** | designer | `data-wave-states` | Which states does it have? e.g. default hover focus disabled loading error. |
| Component | **mandatory** | designer | `data-wave-component` | Which design-system component is this? |
#### checkbox
| Field | Level | Asked of | Written as | Question |
| --- | --- | --- | --- | --- |
| Writes to | **mandatory** | product | `data-wave-field` | Which data does this set? A path such as address/postcode. |
| Label | **mandatory** | designer | the design | It needs a label people and screen readers can read. |
| Starting value | **mandatory** | product | `data-wave-default` | What is it set to at the start? A value, or none. |
| Required and rules | **mandatory** | product | `data-wave-validate` | Must it be ticked (e.g. terms)? required or optional. |
| Acts | recommended | product | `data-wave-commit` | Does ticking it do something immediately (instant) or only on save (save)? |
| Component | **mandatory** | designer | `data-wave-component` | Which design-system component is this? |
#### radio
| Field | Level | Asked of | Written as | Question |
| --- | --- | --- | --- | --- |
| Label | **mandatory** | designer | the design | Each option needs a label. |
#### radioGroup
| Field | Level | Asked of | Written as | Question |
| --- | --- | --- | --- | --- |
| Writes to | **mandatory** | product | `data-wave-field` | Which data does this set? A path such as address/postcode. |
| Label | **mandatory** | designer | the design | The group needs a label (a legend or aria-label). |
| Options | **mandatory** | product | `data-wave-options` | What are the choices, and where do they come from? A list separated by \|, or a data path. |
| Starting value | **mandatory** | product | `data-wave-default` | What is it set to at the start? A value, or none. |
| Component | **mandatory** | designer | `data-wave-component` | Which design-system component is this? |
#### switch
| Field | Level | Asked of | Written as | Question |
| --- | --- | --- | --- | --- |
| Label | **mandatory** | designer | the design | It needs a label people and screen readers can read. |
| Acts | **mandatory** | product | `data-wave-commit` | Does flipping it take effect immediately (instant) or on save (save)? |
| Writes to (if it acts on save) | **mandatory** | product | `data-wave-field` | Which data does this set? A path such as address/postcode. |
| Action (if it acts instantly) | **mandatory** | product | `data-wave-action` | What is this action called? e.g. action/checkout/add-address. |
| Trigger (if it acts instantly) | **mandatory** | product | `data-wave-trigger` | What triggers it: click, submit, change or load? |
| Side effects (if it acts instantly) | **mandatory** | product | `data-wave-effect` | What happens behind it: API calls, analytics, emails, payments? Space separated, or none. |
| On success (if it acts instantly) | **mandatory** | product | `data-wave-to` | Where does the user end up when it works? screen:, node:, modal:, back, url:, or stay. |
| On failure (if it acts instantly and it has side effects) | **mandatory** | product | `data-wave-to-failure` | What happens if it fails? The node that shows the error (node:screen/slug), a screen, or none. |
| Starting value | **mandatory** | product | `data-wave-default` | What is it set to at the start? A value, or none. |
| Component | **mandatory** | designer | `data-wave-component` | Which design-system component is this? |
#### datePicker
| Field | Level | Asked of | Written as | Question |
| --- | --- | --- | --- | --- |
| Writes to | **mandatory** | product | `data-wave-field` | Which data does this set? A path such as address/postcode. |
| Label | **mandatory** | designer | the design | It needs a label people and screen readers can read. |
| Required and rules | **mandatory** | product | `data-wave-validate` | Must it be filled in, and what rules apply (format, length, range)? Say optional if none. |
| Error message drawn (if it has validation rules) | **mandatory** | designer | the design | Draw the error message for each rule (an element with data-wave-state-of pointing here and data-wave-state="error"). |
| States | **mandatory** | designer | `data-wave-states` | Which states does it have? e.g. default hover focus disabled loading error. |
| Component | **mandatory** | designer | `data-wave-component` | Which design-system component is this? |
| Allowed range | **mandatory** | product | `min` | Earliest and latest allowed? Past dates allowed? (min/max attributes, or rules like future-only) |
| Format (for values and dynamic text) | **mandatory** | product | `data-wave-format` | How is the date shown? e.g. date:medium, time:short (and whose time zone). |
#### slider
| Field | Level | Asked of | Written as | Question |
| --- | --- | --- | --- | --- |
| Writes to | **mandatory** | product | `data-wave-field` | Which data does this set? A path such as address/postcode. |
| Label | **mandatory** | designer | the design | It needs a label people and screen readers can read. |
| Minimum | **mandatory** | product | `min` | The minimum value? |
| Maximum | **mandatory** | product | `max` | The maximum value? |
| Step | **mandatory** | product | `step` | The step size? |
| Starting value | **mandatory** | product | `data-wave-default` | What is it set to at the start? A value, or none. |
| Component | **mandatory** | designer | `data-wave-component` | Which design-system component is this? |
#### fileUpload
| Field | Level | Asked of | Written as | Question |
| --- | --- | --- | --- | --- |
| Writes to | **mandatory** | product | `data-wave-field` | Which data does this set? A path such as address/postcode. |
| Label | **mandatory** | designer | the design | It needs a label people and screen readers can read. |
| Accepted types | **mandatory** | product | `accept` | Which file types are accepted? e.g. image/*,.pdf |
| Required and rules | **mandatory** | product | `data-wave-validate` | Maximum size and count? e.g. required; max-size:5MB; max-files:3 |
| Action | **mandatory** | product | `data-wave-action` | What happens to the file: uploaded immediately, or with the form? Name the action. |
| States | **mandatory** | designer | `data-wave-states` | Which states does it have? e.g. default hover focus disabled loading error. |
| Component | **mandatory** | designer | `data-wave-component` | Which design-system component is this? |
#### richTextEditor
| Field | Level | Asked of | Written as | Question |
| --- | --- | --- | --- | --- |
| Writes to | **mandatory** | product | `data-wave-field` | Which data does this set? A path such as address/postcode. |
| Label | **mandatory** | designer | the design | It needs a label people and screen readers can read. |
| Allowed formatting | **mandatory** | product | `data-wave-config` | Which formatting is allowed? e.g. formats:bold italic link list |
| Format (for values and dynamic text) | **mandatory** | product | `data-wave-format` | What does it save as: html, markdown or json? |
| Required and rules | **mandatory** | product | `data-wave-validate` | Required? Maximum length? |
| Component | **mandatory** | designer | `data-wave-component` | Which design-system component is this? |
### Feedback
#### errorMessage
| Field | Level | Asked of | Written as | Question |
| --- | --- | --- | --- | --- |
| Belongs to | **mandatory** | product | the design | What is this the error of? Point data-wave-state-of at the field, or make an action's data-wave-to-failure lead here. |
| Fixed or from data | **mandatory** | product | `data-wave-content` | Fixed text, or the message the server returns? |
| Copy status (if fixed text) | recommended | product | `data-wave-copy` | Is this the final copy? final, draft or placeholder. |
#### emptyState
| Field | Level | Asked of | Written as | Question |
| --- | --- | --- | --- | --- |
| Shown when | **mandatory** | product | the design | When does it show? Point a list's data-wave-empty-state here, or give it data-wave-visible-if. |
#### loadingState
| Field | Level | Asked of | Written as | Question |
| --- | --- | --- | --- | --- |
| Loading of | **mandatory** | designer | the design | What is this the loading state of? Point data-wave-state-of at it. |
| Component | **mandatory** | designer | `data-wave-component` | Which design-system component is this? |
#### toast
| Field | Level | Asked of | Written as | Question |
| --- | --- | --- | --- | --- |
| Shown by | **mandatory** | product | the design | What shows it? Point an action's data-wave-feedback here, or give it data-wave-visible-if. |
| Variant | **mandatory** | designer | `data-wave-variant` | Severity: info, success, warning or error? |
| Goes away | **mandatory** | product | `data-wave-dismiss` | Does it hide by itself (auto:5) or need closing (close-button)? |
| Component | **mandatory** | designer | `data-wave-component` | Which design-system component is this? |
#### tooltip
| Field | Level | Asked of | Written as | Question |
| --- | --- | --- | --- | --- |
| Opens on | **mandatory** | product | `data-wave-trigger` | Hover, focus or click? |
| Describes | **mandatory** | designer | the design | Which element does it describe? Point data-wave-state-of at it. |
| Fixed or from data | **mandatory** | product | `data-wave-content` | Is this text fixed, or does it come from data? |
### Behaviours
Written in `data-wave-behavior`; each needs its settings in `data-wave-config` (key:value; key:value) and some attributes.
| Behaviour | Settings | Attributes |
| --- | --- | --- |
| carousel | autoplay, loop, controls | none |
| reorderable | keyboard | data-wave-action |
| draggable | drops | data-wave-action |
| drop-target | none | data-wave-action |
| accordion | open, multiple | none |
| collapsible | open | none |
| infinite-scroll | none | data-wave-paginate |
| swipe-actions | none | none |
| sticky | position | none |
| pull-to-refresh | none | data-wave-action |
| copy-to-clipboard | copies | data-wave-feedback |Wave Figma
For Claude Code: start here when the design is in Figma. Runs the Figma flow in three stages; the engineer answers questions, the designer fixes Figma and approves.
---
name: Wave Figma
description: Start here when a design is in Figma. Brings a Figma file into Wave in Post-it through three skill-driven stages (Wave Figma Brief for DESIGN.md, Wave Figma Design System for tokens and specimens, Wave Figma Feature for screens, review and the prototype). Claude runs every command; the engineer answers questions; the designer fixes Figma and approves in Post-it.
---
# Wave Figma
The engineer says something like "Bring this Figma file into Wave:
<link>". You do everything else, stage by stage, asking only questions.
| Stage | Skill | Makes | Waits for |
| --- | --- | --- | --- |
| 1 | `wave-figma-brief` | The project and DESIGN.md | The engineer's answers |
| 2 | `wave-figma-design-system` | Tokens, one specimen per component, the design-system page | A ready file; the designer's approval |
| 3 | `wave-figma-feature` | Screens, FEATURE.md, review, the prototype | A ready file; the designer's review |
## Start
1. **Setup, once per machine, without asking.** Check `node --version` (20 or
later) and `npx playwright --version` with Chromium. Download the command
line: `curl -sSfo wave-figma.mjs <post-it>/wave/wave-figma.mjs`, where `<post-it>` is the Post-it connector's address without `/api/mcp`. Run it as
`node wave-figma.mjs <command>`. Only if something is missing, ask the
engineer before installing it.
2. **Check access, once.** One Figma call on the file (`use_figma` needs edit
access even to read; a view seat cannot run the scripts) and one Post-it call.
If either fails, stop and say so.
3. Ask which **project** (and, for screens, which **feature**) this is.
- Find it with `list_spaces` and `list_tree` (projects and features are marked).
- No project yet: `create_folder` with `project: true` (it creates design-system/ and
design-system/components). No feature yet: `create_folder` inside the project with
`flow: true`.
4. Read **Wave Figma progress** in the project if it exists, and carry on from
where it stands. Otherwise create it and start at stage 1.
5. Load the stage's skill with `get_skill` (space postit, path
`skills/<name>`) and follow it exactly. A stage that is done is not run
again unless Figma changed.
## Rules for every stage
- **The engineer never runs a command.** You run every `wave-figma` command
and every Post-it and Figma tool yourself, read the JSON each prints, and only
ever ask the engineer questions, show results and ask for decisions.
- **Interviews, not forms.** Ask in small groups (at most five questions at a
time), each with a proposal taken from Figma or DESIGN.md, so "yes" accepts
it. Never ask what the file already says. Say which stage you are in and
what comes next.
- **Exactly as drawn, or not at all.** Wave refuses a Figma file it cannot
convert to exactly the same page: anything the entry gate marks as blocking,
any page that does not match Figma's own render, any font Wave cannot serve.
Never work around one: no value read off a screenshot, no layer redrawn in
HTML, no font swapped for a similar one, no change to Wave to fit the file.
- **The designer fixes Figma.** When the file is not ready, write the
**Figma readiness report** (below), publish it in Post-it, give the engineer
its link to send to the designer, and stop at "waiting for the designer".
You may offer to edit the Figma file yourself, but only by asking twice:
first "Wave could make these corrections in the Figma file itself. That
changes the designer's file. Do you want me to edit it?", and only after a
yes, "Please confirm the designer has agreed to me changing
<file name>. Edit it now?". Anything but two clear yeses means no; engineers
usually may not edit the design, so expect no and do not argue. If you do
edit, list every change and whether it moved a pixel.
- **The designer approves.** Specimens and screens are approved by the
designer in Post-it (review, then approve), never by the engineer and never by
you: do not call `approve_page`. Designer feedback arrives as Post-it
comments; read them with `list_comments`, act on them, and answer with
`mark_addressed`.
- **If anything cannot be reached** (Figma, Post-it, a font, an image, the
command line), stop and say exactly what failed. Never take another route to
the same result.
- **Keep it safe.** Ask Post-it for an upload link (`wave_upload_link`) for each
step that sends files, use it only in `wave-figma send --link`, and never
write it to a file, a page or a message. Never ask the engineer for a token.
## The readiness report
`wave-figma report --gate gate.json -o REPORT.md --fonts [--fidelity results.json] --title "<project> <stage>: Figma readiness"`
writes it: whether Wave can take the file, then every correction by component
and screen, in plain words, with a Figma link for each, and the pages that do
not match Figma (`results.json`: `[{name, node, score, pass, cause}]`, one
for each fidelity run, with the cause in a sentence when you know it).
Publish it as the article **Figma readiness report** in the project (design
system) or the feature (screens), replacing the last one, and give the link.
## The progress page
Keep the article **Wave Figma progress** in the project folder up to date
after every step: each stage and step with done, waiting (on whom, for what)
or to do; the open questions; the links (readiness report, design-system page,
review, prototype). Any session starts by reading it and carries on from
there; tell the engineer where things stand in one line.
Wave Figma Brief
Figma flow, stage 1: DESIGN.md from the Figma file, asking only what Figma cannot say.
---
name: Wave Figma Brief
description: Stage 1 of the Figma flow. Writes the project's DESIGN.md in Post-it from a Figma file, asking the engineer only what Figma cannot say. Load it from Wave Figma.
---
# Wave Figma Brief
DESIGN.md holds the defaults every element inherits and the design language
Wave checks against. Most of it is in the Figma file; ask for the rest.
## 1. Read the file
1. `wave-figma script INVENTORY` (pages, frames and their widths, components,
text), `script VARIABLES` and `script STYLES`: run each with `use_figma`,
save the result, and check it with `wave-figma checksum` (a result too big
for one reply comes in parts, `--part n`).
2. From them, draft what Figma says:
- **Viewports**: the frame widths of the screens.
- **Design language**: colours, type, spacing, shape and elevation from the
variables and styles; voice from the copy on the screens.
- **Components**: the component sets and components on the design-system
page, with their variants.
- **Forms**: an Error variant with a message means errors show per field;
a disabled submit variant means submit waits for valid input.
- **Language** of the copy.
## 2. Interview the engineer
`wave_get_brief` (kind design) gives the template and any DESIGN.md already
saved. Ask only what is still open, in groups, each with a proposal:
1. **Copy**: final, draft or placeholder; where it will live (code, a CMS,
translation keys).
2. **Access**: who may open the screens (public, signed in, a role).
3. **Analytics**: tracked or not; the event naming.
4. **Data**: what an empty value shows; what long text does.
5. **Anything the engineer knows that Figma does not**: feature flags,
platforms, accessibility targets.
## 3. Save
Show DESIGN.md in full and ask: "Save this as the project's DESIGN.md?" On a
yes, `wave_save_brief` (kind design). Fix every problem it lists. Mark stage
1 done in **Wave Figma progress**, then load `wave-figma-design-system`.
## Rules for every stage
- **The engineer never runs a command.** You run every `wave-figma` command
and every Post-it and Figma tool yourself, read the JSON each prints, and only
ever ask the engineer questions, show results and ask for decisions.
- **Interviews, not forms.** Ask in small groups (at most five questions at a
time), each with a proposal taken from Figma or DESIGN.md, so "yes" accepts
it. Never ask what the file already says. Say which stage you are in and
what comes next.
- **Exactly as drawn, or not at all.** Wave refuses a Figma file it cannot
convert to exactly the same page: anything the entry gate marks as blocking,
any page that does not match Figma's own render, any font Wave cannot serve.
Never work around one: no value read off a screenshot, no layer redrawn in
HTML, no font swapped for a similar one, no change to Wave to fit the file.
- **The designer fixes Figma.** When the file is not ready, write the
**Figma readiness report** (below), publish it in Post-it, give the engineer
its link to send to the designer, and stop at "waiting for the designer".
You may offer to edit the Figma file yourself, but only by asking twice:
first "Wave could make these corrections in the Figma file itself. That
changes the designer's file. Do you want me to edit it?", and only after a
yes, "Please confirm the designer has agreed to me changing
<file name>. Edit it now?". Anything but two clear yeses means no; engineers
usually may not edit the design, so expect no and do not argue. If you do
edit, list every change and whether it moved a pixel.
- **The designer approves.** Specimens and screens are approved by the
designer in Post-it (review, then approve), never by the engineer and never by
you: do not call `approve_page`. Designer feedback arrives as Post-it
comments; read them with `list_comments`, act on them, and answer with
`mark_addressed`.
- **If anything cannot be reached** (Figma, Post-it, a font, an image, the
command line), stop and say exactly what failed. Never take another route to
the same result.
- **Keep it safe.** Ask Post-it for an upload link (`wave_upload_link`) for each
step that sends files, use it only in `wave-figma send --link`, and never
write it to a file, a page or a message. Never ask the engineer for a token.
## The readiness report
`wave-figma report --gate gate.json -o REPORT.md --fonts [--fidelity results.json] --title "<project> <stage>: Figma readiness"`
writes it: whether Wave can take the file, then every correction by component
and screen, in plain words, with a Figma link for each, and the pages that do
not match Figma (`results.json`: `[{name, node, score, pass, cause}]`, one
for each fidelity run, with the cause in a sentence when you know it).
Publish it as the article **Figma readiness report** in the project (design
system) or the feature (screens), replacing the last one, and give the link.
## The progress page
Keep the article **Wave Figma progress** in the project folder up to date
after every step: each stage and step with done, waiting (on whom, for what)
or to do; the open questions; the links (readiness report, design-system page,
review, prototype). Any session starts by reading it and carries on from
there; tell the engineer where things stand in one line.
Wave Figma Design System
Figma flow, stage 2: tokens and specimens from Figma, or a readiness report for the designer when Wave cannot convert the file exactly.
---
name: Wave Figma Design System
description: Stage 2 of the Figma flow. Checks the Figma design-system page, refuses it with a readiness report for the designer if Wave cannot convert it exactly, otherwise builds the tokens and one specimen per component in Post-it, gets the designer's approval there, and writes the design-system page. Load it from Wave Figma.
---
# Wave Figma Design System
No screen is converted until the catalogue is approved.
## 1. Is the file ready?
1. Ask which page is the design-system page (propose it from the page names).
2. `wave-figma script GATE --page <design-system page> --ids <design-system page>`,
run with `use_figma`, save as `gate.json`, checksum it.
3. `wave-figma gate --report gate.json --fonts`. If anything blocks or a font
cannot be served: write and publish the **readiness report**, tell the
engineer in two or three sentences what the designer has to change, give
the link, offer the Figma edit only as the rules say, and stop. When the
designer says it is done, start again at step 2.
## 2. Tokens and fonts
1. `script VARIABLES` and `script STYLES` (as in stage 1), then
`wave-figma tokens --variables vars.txt --styles styles.json -o tokens.json`:
it must report no problems. Save it as the JSON page `design-system/tokens`.
2. `wave-figma fonts --families "<families from the gate>" --out fonts/`, then
`upload_asset` for each file, then `wave-figma font-css --manifest fonts/fonts.json --urls urls.json -o fonts.css`.
## 3. One specimen per component
For each component set and component on the page, without asking:
1. `script COMPONENT --node <set>` (save as `component.json`),
`script BINDINGS --ids <set>`, `script EFFECTS --node <set>`,
`script EXPORT_SVG --ids <its vectors>`, Figma's `get_design_context` and
`get_screenshot`.
2. `wave-figma convert --gate gate.json --component component.json --type <element type>
--code code.tsx --width <w> --height <h> --tokens tokens.json --bindings bindings.txt
--effects effects.json --svgs svgs.json --fonts fonts.css -o specimen.html`.
The element type is Wave's (button, textInput, checkbox, radio, select,
navigation, icon, text...). Ask the engineer only when two fit ("Is Option
card a checkbox or a radio?").
3. `wave-figma align`, then `wave-figma fidelity --component component.json`
against the screenshot. Record every result for the report.
4. Where Figma drew a picture of a control, an upgrade plan makes the real
element (`wave-figma upgrade --plan`); the look lock must be clean.
5. `wave-figma ids` (with `--from <published specimen>` when there is one),
then `wave-figma preflight`, then Post-it's `preflight_html` (target = the
project).
If any specimen does not match Figma, the file is **not ready**: publish the
readiness report with the fidelity results and stop, as in 1.3.
## 4. Publish for the designer
1. `wave_upload_link`, then `wave-figma send --link <link> --tool create_page`
(or `update_page` for a new version) with `--file content=specimen.html`,
one call per specimen, into `design-system/components`.
2. `wave_design_system_page`: it writes the design-system page and its JSON
from the specimens. Never write that table by hand.
3. `ask_for_review` on each specimen. Tell the engineer: "The design system is
ready for the designer to review in Post-it: <link to the design-system page>.
They compare each specimen with Figma, comment on anything wrong, and
approve." Record "waiting for the designer" in **Wave Figma progress**.
## 5. The designer's answer
When the engineer comes back:
1. `list_comments` on the specimens. A comment about the conversion (a page
that differs from Figma): fix it in Wave's output only if the fix keeps the
page exactly as Figma draws it, publish the new version and
`mark_addressed`. A comment that needs a change in Figma: it goes in the
readiness report for the designer, as in 1.3.
2. `read_page` each specimen: when its review is **approved** (by the
designer, in Post-it), set its definition's `"status": "approved"` and publish
that version.
3. When every specimen is approved, run `wave_design_system_page` again, mark
stage 2 done, and load `wave-figma-feature`.
## Rules for every stage
- **The engineer never runs a command.** You run every `wave-figma` command
and every Post-it and Figma tool yourself, read the JSON each prints, and only
ever ask the engineer questions, show results and ask for decisions.
- **Interviews, not forms.** Ask in small groups (at most five questions at a
time), each with a proposal taken from Figma or DESIGN.md, so "yes" accepts
it. Never ask what the file already says. Say which stage you are in and
what comes next.
- **Exactly as drawn, or not at all.** Wave refuses a Figma file it cannot
convert to exactly the same page: anything the entry gate marks as blocking,
any page that does not match Figma's own render, any font Wave cannot serve.
Never work around one: no value read off a screenshot, no layer redrawn in
HTML, no font swapped for a similar one, no change to Wave to fit the file.
- **The designer fixes Figma.** When the file is not ready, write the
**Figma readiness report** (below), publish it in Post-it, give the engineer
its link to send to the designer, and stop at "waiting for the designer".
You may offer to edit the Figma file yourself, but only by asking twice:
first "Wave could make these corrections in the Figma file itself. That
changes the designer's file. Do you want me to edit it?", and only after a
yes, "Please confirm the designer has agreed to me changing
<file name>. Edit it now?". Anything but two clear yeses means no; engineers
usually may not edit the design, so expect no and do not argue. If you do
edit, list every change and whether it moved a pixel.
- **The designer approves.** Specimens and screens are approved by the
designer in Post-it (review, then approve), never by the engineer and never by
you: do not call `approve_page`. Designer feedback arrives as Post-it
comments; read them with `list_comments`, act on them, and answer with
`mark_addressed`.
- **If anything cannot be reached** (Figma, Post-it, a font, an image, the
command line), stop and say exactly what failed. Never take another route to
the same result.
- **Keep it safe.** Ask Post-it for an upload link (`wave_upload_link`) for each
step that sends files, use it only in `wave-figma send --link`, and never
write it to a file, a page or a message. Never ask the engineer for a token.
## The readiness report
`wave-figma report --gate gate.json -o REPORT.md --fonts [--fidelity results.json] --title "<project> <stage>: Figma readiness"`
writes it: whether Wave can take the file, then every correction by component
and screen, in plain words, with a Figma link for each, and the pages that do
not match Figma (`results.json`: `[{name, node, score, pass, cause}]`, one
for each fidelity run, with the cause in a sentence when you know it).
Publish it as the article **Figma readiness report** in the project (design
system) or the feature (screens), replacing the last one, and give the link.
## The progress page
Keep the article **Wave Figma progress** in the project folder up to date
after every step: each stage and step with done, waiting (on whom, for what)
or to do; the open questions; the links (readiness report, design-system page,
review, prototype). Any session starts by reading it and carries on from
there; tell the engineer where things stand in one line.
Wave Figma Feature
Figma flow, stage 3: screens, FEATURE.md interview, review and prototype, or a readiness report when the screens cannot be converted exactly.
---
name: Wave Figma Feature
description: Stage 3 of the Figma flow. Checks a feature's Figma screens, refuses them with a readiness report if Wave cannot convert them exactly, otherwise converts them against the approved catalogue, interviews the engineer for FEATURE.md, runs the dry run, publishes the feature to Post-it for the designer's review and makes the prototype. Load it from Wave Figma.
---
# Wave Figma Feature
Needs an approved catalogue (stage 2). Ask which feature and which frames;
propose the frames from the screens page and their prototype links, in order.
## 1. Are the screens ready?
`wave-figma script GATE --page <design-system page> --ids <design-system page>,<screens page>`,
save, checksum, `wave-figma gate --report gate.json --fonts`. Anything
blocking: readiness report for the feature, the link, the Figma edit only as
the rules say, stop.
## 2. Convert each screen, without asking
1. `script NODE_MAP --node <frame>` (save as `map.json`), `script BINDINGS --ids <frame>`,
`EFFECTS --node <frame>`, `EXPORT_SVG --ids <its vectors>`, `get_design_context`,
`get_screenshot`.
2. `wave-figma convert --gate gate.json --components <dir of component.json>
--specimen-pages <published specimens> --map map.json --screens screens.json
--code code.tsx --width <w> --height <h> --tokens tokens.json --bindings bindings.txt
--effects effects.json --svgs svgs.json --fonts fonts.css --source figma:<file>/<frame> -o screen.html`.
`screens.json` maps frame names to screen slugs (propose the slugs).
3. `align`, `fidelity` (record every result), `upgrade --plan` (look lock
clean), `ids` (`--from` the published screen when there is one),
`preflight`.
4. The plan says what Figma cannot draw, in the design's own words: a heading
is `h1`, a form is `form`, a group of chips is a `radiogroup` with its
field, a decorative icon is `aria-hidden`; `data-wave-role` where Wave
would guess wrong. Ask the engineer only where the design does not decide.
A screen that does not match Figma makes the feature **not ready**: readiness
report with the fidelity results, stop.
## 3. Interview the engineer for FEATURE.md
`wave_get_brief` (kind feature) gives the template. Figma already says the
screens, the components, where each button goes (prototype links) and each
field's error message (the Error variant's text). Draft FEATURE.md from that,
then ask screen by screen, in groups, each with a proposal:
1. **Fields**: what each one writes, required or not, rules, options, default.
2. **Data**: what each screen shows, where it comes from, what empty shows.
3. **Actions**: what each button does, where it goes when it works and when it
fails, whether it asks to confirm.
4. **Anything else open** after `wave_dry_run` on the converted screens.
A question that does not apply is **waived** with a reason the engineer
agrees to (data the screen never fetches, a group the catalogue has no
component for). Show FEATURE.md in full and `wave_save_brief` (kind feature)
on a yes. Run `wave_dry_run` again until it passes, applying answers with
`wave_apply_answers`.
## 4. Publish for the designer
1. Preflight every screen with Post-it's `preflight_html` (target = the feature).
2. Show the engineer the screens and the report (fidelity per screen, every
waiver) and ask: "Publish these for the designer's review?"
3. On a yes: `wave-figma bundle --screen "<Name>=<file>,..." -o screens.json`,
`wave_upload_link`, then `wave-figma send --link <link> --tool wave_publish_flow
--args '{"feature_id":"<id>"}' --json-file screens=screens.json`.
4. Make the prototype (the Wave Review skill's prototype section): without an
API, actions simulate loading and the viewer picks success or failure.
5. `ask_for_review` on each screen. Give the engineer the review links and
the prototype link for the designer. Record "waiting for the designer".
## 5. The designer's answer
As in stage 2: comments about the conversion are fixed in Wave's output only
if the page stays exactly as Figma draws it; anything that needs Figma goes in
the readiness report. When every screen is approved in Post-it, the feature can
be approved and handed over: engineers build it with **Wave Build**. Mark the
feature done in **Wave Figma progress**.
## When Figma changes
Start the stage again from its check: gate, convert with `ids --from` the
published version (check anything reported as vanished), publish the new
versions, and run `wave_design_system_page` again after specimens change.
## Rules for every stage
- **The engineer never runs a command.** You run every `wave-figma` command
and every Post-it and Figma tool yourself, read the JSON each prints, and only
ever ask the engineer questions, show results and ask for decisions.
- **Interviews, not forms.** Ask in small groups (at most five questions at a
time), each with a proposal taken from Figma or DESIGN.md, so "yes" accepts
it. Never ask what the file already says. Say which stage you are in and
what comes next.
- **Exactly as drawn, or not at all.** Wave refuses a Figma file it cannot
convert to exactly the same page: anything the entry gate marks as blocking,
any page that does not match Figma's own render, any font Wave cannot serve.
Never work around one: no value read off a screenshot, no layer redrawn in
HTML, no font swapped for a similar one, no change to Wave to fit the file.
- **The designer fixes Figma.** When the file is not ready, write the
**Figma readiness report** (below), publish it in Post-it, give the engineer
its link to send to the designer, and stop at "waiting for the designer".
You may offer to edit the Figma file yourself, but only by asking twice:
first "Wave could make these corrections in the Figma file itself. That
changes the designer's file. Do you want me to edit it?", and only after a
yes, "Please confirm the designer has agreed to me changing
<file name>. Edit it now?". Anything but two clear yeses means no; engineers
usually may not edit the design, so expect no and do not argue. If you do
edit, list every change and whether it moved a pixel.
- **The designer approves.** Specimens and screens are approved by the
designer in Post-it (review, then approve), never by the engineer and never by
you: do not call `approve_page`. Designer feedback arrives as Post-it
comments; read them with `list_comments`, act on them, and answer with
`mark_addressed`.
- **If anything cannot be reached** (Figma, Post-it, a font, an image, the
command line), stop and say exactly what failed. Never take another route to
the same result.
- **Keep it safe.** Ask Post-it for an upload link (`wave_upload_link`) for each
step that sends files, use it only in `wave-figma send --link`, and never
write it to a file, a page or a message. Never ask the engineer for a token.
## The readiness report
`wave-figma report --gate gate.json -o REPORT.md --fonts [--fidelity results.json] --title "<project> <stage>: Figma readiness"`
writes it: whether Wave can take the file, then every correction by component
and screen, in plain words, with a Figma link for each, and the pages that do
not match Figma (`results.json`: `[{name, node, score, pass, cause}]`, one
for each fidelity run, with the cause in a sentence when you know it).
Publish it as the article **Figma readiness report** in the project (design
system) or the feature (screens), replacing the last one, and give the link.
## The progress page
Keep the article **Wave Figma progress** in the project folder up to date
after every step: each stage and step with done, waiting (on whom, for what)
or to do; the open questions; the links (readiness report, design-system page,
review, prototype). Any session starts by reading it and carries on from
there; tell the engineer where things stand in one line.
Wave Build
For Claude Code: build an approved Wave flow from its handover, with get_handover and get_handover_screen.
---
name: Wave Build
description: Build an approved flow of Wave mockups from its handover in Post-it. Use when asked to implement screens that were designed and approved with Wave.
---
# Wave Build
An approved Wave flow is a complete, frozen spec: HTML screens whose
`data-wave-*` attributes say what every element is, says and does, the
project's DTCG tokens, the catalogue specimens of every component used, the
assets, the answer sheet, and the decisions made in review.
1. Call `get_handover` with the flow's id. If it refuses, it lists what is
blocking approval: stop and report that, do not build from unapproved
screens.
2. Read HANDOVER.md from the answer end to end: routes, the flow graph, the data
dictionary, the action catalog with side effects and destinations, component
states, and the review decisions and accepted gaps.
3. Build components first, from `components/*.html` (the catalogue
specimens): one code component per specimen, with every variant and state
drawn there. Map tokens by name (`var(--color-brand-500)` is
`color.brand.500`), never by value.
4. Fetch each screen with `get_handover_screen` as you build it. Every element
with `data-wave-component` is an instance of a catalogue component.
5. Bind every `data-wave-bind` to the resource named, render `data-wave-empty`
when it is empty, apply `data-wave-format` and `data-wave-overflow`. Build
every state listed in `data-wave-states`, using the depicted states
(`data-wave-state-of`) as the design for each.
6. Wire each `data-wave-action` with its `data-wave-effect`s, navigate to
`data-wave-to` on success and `data-wave-to-failure` on failure, honour
`data-wave-confirm`, `data-wave-disabled-if`, `data-wave-visible-if`,
`data-wave-access` and `data-wave-flag`.
7. Copy the files in `assets/` into the codebase (the manifest maps each
hosted address to its file).
8. `api/openapi.json` (and `api/mocks/`, `api/data-requirements.md`) is
the mock API the prototype ran on: the contract the screens were designed
against. Build the data layer to it (`x-wave-provides` names the data
root a GET returns; `x-wave-effect` names the action that calls an
operation), and serve its examples with MSW in development and tests until
the real API exists. Where the real API differs, say so.
9. Where the handover lists an accepted gap or a waived field, follow its note;
where something is neither specified nor waived, ask rather than guess.
Two questions people ask
If I publish a page, does Claude see the comments on it? No. Publishing a page puts the document on the internet; the conversation about it stays behind a login. Comments tend to be candid in a way the document is not, and one toggle should not put them in public.
Can somebody work out that a page exists by searching for a word in it? No. Search runs under your permissions, so a word that appears only in a page you cannot read returns nothing at all, rather than a result with the content hidden. The same is true of links: a link to a page you may not read renders identically to a link to a page that does not exist.