Figma survey
A survey reads two things — the design, and whatever the app already builds for it — and writes two:
- The design spec, the durable half: what the design asks for, with a decision recorded wherever the design and the app disagree. A later run looks a value up here instead of deciding it again, which is the whole point — an agent with no memory of the last session invents a plausible value rather than reaching for the settled one, and by the fifth view the app reads as several products.
- The delta, the transient half: what the design shows that the app does not yet do, written checkable so each item can become a ticket's acceptance-criteria line.
It writes no application code, no theme CSS, and no ticket. Applying the spec belongs
to figma-theme (the global theme) or to implementation (a view). Filing the delta belongs
to a separate ticketing pass, run on this conversation once the user has read the delta.
The survey ends in the conversation, and that pause is what lets the expensive judgement
calls be reviewed before a ticket is worded or a diff is started.
Applies to Vaadin Flow (Java) on the Aura theme.
Two rules that shape every step
Facts are yours; decisions are the user's. Anything readable from the design, the code
or the existing spec, you look up. Anything that picks one side over the other, you put to
the user in rounds, each question carrying your recommendation — the method is
interview.md. The spec is written only once the interview's frontier
is empty.
A survey never boots the app. Its three inputs — the design, the code, the existing spec — are all readable without one, and a running app can only show the scale you are about to replace. A figure only a browser produces (a contrast ratio, a rendered pixel size, a resolved token) is left marked unverified for whoever applies the spec. An unverified row said to be unverified costs the next run five minutes; the same row presented as settled costs it a wrong decision.
Scope
$ARGUMENTS fixes one of three scopes. The scope decides which branch file supplies
the reading and the writing in steps 2, 5 and 6:
| Scope | Target | Writes | Branch |
|---|---|---|---|
| theme | the design's global variables, across every mode | foundations/, and the inputs in tokens/ |
theme-scope.md |
| view | one frame — a screen or a dialog | components/ for the components on it, plus the delta |
view-scope.md |
| component | one component, wherever it appears | that component's file in components/ |
view-scope.md |
Do the theme scope first, and once. It decides the font family and base size, which reflow every screen; per-view spacing settled against the old scale is work thrown away.
Steps
1. Resolve the target and find the spec's home
$ARGUMENTS names a scope, or a view or component in whatever words came to hand —
"Add Expense", LineEditorDialog, report detail — and may include a Figma URL.
Fix the design file first: take fileKey and nodeId from a URL of the form
figma.com/design/:fileKey/:name?node-id=1-2. Absent a URL, check whether the project
records a design file (its agent instructions are the usual home) before asking for one.
Resolve the target on the design side with get_metadata on the page to list frame
names; the branch file says how to pick the frame. Name the resolution back to the user,
and ask only when two candidates are genuinely indistinguishable.
The spec's home is the directory the project's agent instructions name. Read what is already there — it is the baseline this run revises, not a blank page. No spec at all means nothing has been settled yet, which makes every divergence in step 4 open by default; say so, because it bounds what this survey can conclude.
Done when the scope is fixed, one Figma node id is fixed, and the existing spec is either read or its absence noted.
2. Read both ends
Follow the branch file's reading sections: theme reads the design's variables through their bindings; view and component find what the code already builds, read the frame, and map every component instance to a Vaadin component.
Everything in this step is fact-finding. Where a branch file marks a row as judgement — an
override or invented mapping, a value inferred from a drawing — carry it forward as a
question for step 4 rather than settling it here.
Done when the branch file's reading criteria are met and every judgement call it surfaced is on the list for step 4.
3. Establish the app's values — from files
Take the formulas from the spec's token reference — round(baseRadius * 2px + 3px, 1px) and its siblings. They are what the framework derives each scale with, they are
stable across a theme change, and they go stale only on a framework upgrade, which is why
the spec caches them. Then list every property the app's own theme file sets, so step 4
knows what is a deliberate app choice rather than a default.
Compute the resolved values; do not measure them. Given the formulas this is arithmetic, and it is the only correct way round — step 4 is about to change an input, so the numbers you need are the ones at the decided inputs, not the ones the app renders today. Scored against the outgoing base, the wrong base can look better: a type scale whose small step is a clamped fraction of the medium step lands a design's most-used size exactly on a token at one base and between two tokens at another.
Where the spec has no formulas — a first run, or a framework upgrade since the last one — say so and stop short of asserting any resolved number or off-scale verdict. Those rows stay unresolved for a measurement pass to fill; obtaining formulas belongs to whoever applies the theme and is already in a browser.
Done when the formulas are in hand or their absence is recorded, the resolved values at the decided inputs are computed rather than measured, the version they hold for is noted, and every property the app's theme sets is listed.
4. Interview: turn every judgement into a decision
Build the design tree of everything steps 2 and 3 left to judgement, and work it in
rounds per interview.md. Its material, across every scope:
- Divergences — one per property where the design and the app disagree. Properties that already agree need no row and no question.
- Off-scale values — solve the formulas backwards and the design's raw pixel values disagree with each other: a 9 px field radius asks for one base radius and a 12 px card radius for another, at which the field renders 6 px. Each such value sits between two steps of the scale or beyond its end, and each is a global decision with four options: correct the design back to the scale, override the derived property globally, define one project-level property used everywhere that value appears, or accept the nearest token and record the difference. Recommend a property only for a value several pixels off and recurring; within a pixel or two, the nearest token — a property shadowing the scale for one pixel is where drift starts. Where the framework has a real property for exactly that value, override it rather than inventing a twin.
- Judgement calls the branch file surfaced — mappings, accepted infidelities, domain gaps.
Two constraints are facts to report, never questions:
- The accessibility floor. Contrast, touch-target size and focus visibility are constraints. A design value that breaks one is a design defect; the app keeps the accessible value, recorded as a settled divergence.
- A value the design contradicts itself on. Where a variable and the drawn value disagree, the variable wins, and the drawn value is a design defect to report.
Done when the frontier is empty and the user has confirmed the tree: every property is either identical on both sides or carries a decision with a status — settled or open — that the user chose.
5. Write the spec
Write into the project's spec directory, following any template its own README defines
— where it does, that is binding and overrides the branch file. The branch file says which
files and what each covers.
Every decision row carries settled or open. A divergence resolved in the app's favour is settled, not absent — that row is what stops the next survey reporting the app's own font as a mismatch. A concern with no divergence still gets its file or its section saying so; absence recorded is what stops the next survey re-deriving it. A figure only a browser can produce stays unverified.
Done when every file the scope calls for exists, every decision row carries a status, and no measured figure is asserted without a measurement.
6. Hand over in the conversation
Write the hand-over as your final message, under the headings the branch file gives. It is the input to a ticketing pass, so it must stand without this conversation: a reader lifting it into a ticket should never need to re-read the frame.
Global theming differences do not go in a view's delta. They were decided in step 4 and written in step 5, which is what stops the next survey re-litigating the same radius.
The survey stops here. Name the ticketing pass as the next step and leave it to the user; the delta is theirs to read before it becomes anyone's work.
Done when every heading is filled or explicitly marked empty, the spec files are listed, and the message ends with the next step named.