Spirit Design System — Review Figma Design
This skill guides you through a structured design review from a Spirit Design System perspective,
matching the team's handoff process. The output is a written report (for the ticket) and, if issues
are found, a list of Figma comments to be added.
Invocation
/spirit:review-figma-design [type] [issue-id] [figma-url] [--post-comments-to-figma]
All arguments are optional and positionally flexible — each is unambiguous by its format:
type is one of page, component, composition; issue-id matches [A-Z]+-\d+ (e.g.
DS-2475); figma-url starts with https://; --post-comments-to-figma is a literal flag.
[type] — When omitted, auto-detected from the frame structure (see Step 1); falls back to
composition if detection is inconclusive. page is never auto-detected — pass it explicitly
when reviewing a full page.
| Type |
When to use |
page |
Full page design — includes the Section/Container structure check |
component |
A single DS component being designed or updated |
composition |
A multi-component composition that is not a full page |
[issue-id] — optional JIRA issue ID (e.g. DS-2475). When provided, it is prepended to the
output directory name: design-reviews/DS-2475-reply-form/. When omitted, the directory uses only
the frame-name slug.
[figma-url] — optional Figma frame URL. Two behaviours:
- Omitted — review the frame currently selected in the Figma desktop app. Call
get_metadata
without a nodeId.
- Provided — extract the
nodeId from the URL and use it for all Figma MCP calls. The node ID
is the node-id query parameter with - replaced by : (e.g. node-id=34675-59177 →
nodeId: "34675:59177").
[--post-comments-to-figma] — optional flag. When present, all proposed Figma comments are
posted automatically at the end of the review without asking for confirmation (see Step 13).
Checks that apply only to specific evaluation types are marked accordingly throughout this skill.
Prerequisites
- The Figma desktop app must be open — it exposes the Figma MCP server.
- Without a URL — the target frame must be selected in the Figma desktop app.
- With a URL — no frame selection is needed; the URL identifies the target node.
- You must have access to the Spirit component codebase (for cross-referencing component existence).
Workflow
Step 1: Fetch Design Data
Determine the target node:
- No URL provided — call
get_metadata without a nodeId (uses current Figma selection).
If the call fails with an error such as "fileKey is required", the MCP server in this
environment needs a file key. Stop and ask the user to provide a Figma URL, then proceed
with the URL-based path below.
- URL provided — extract the
nodeId from the URL (see Invocation section) and pass it to
all subsequent Figma MCP calls. If the URL has no node-id parameter, call get_metadata
without a nodeId as well.
Detect multi-frame mode — inspect the root node returned by get_metadata:
- If the root is a page or canvas (its direct children are
<frame> elements rather than
component layers), the URL points to a zoomed area or page rather than a single frame.
- List the frames numbered and ask:
This URL contains N frames:
- Frame One
- Frame Two
…
Review all N frames, or pick one? Enter a number to pick a single frame, or
all.
- Wait for the user's answer before continuing.
- User picks a number — review only that frame in single-frame mode (continue with
step 3 below, using the picked frame's node ID as the target).
- User answers
all — review all frames together and produce a single combined report.
Run Steps 3–8 for each <frame> child in order (collecting findings across all frames), then
write one report (Steps 9–11) that covers all frames. See Step 9 for the output path and
Step 10 for the Frames Reviewed section format.
- Single-frame mode (the normal case): the root node is already a single frame — continue
with step 3 below.
If [type] was not provided, auto-detect it from the get_metadata output:
- Inspect the direct children of the root frame.
- If all direct children are
<symbol> elements with names in Property=Value format
(e.g. Color=Primary, Size=Medium), set type to component.
- Otherwise, set type to
composition.
- Log the detected type so the user can see which was chosen.
- If
[type] was provided, use it as-is — skip detection entirely.
Call get_design_context on the root node to extract:
- All component instances and their Code Connect snippets
- All token references (spacing, color, typography, radius, shadow)
- Raw hardcoded values (CSS literals without
var(--...))
- Any custom/raw HTML compositions that lack Code Connect mappings
Call get_variable_defs on the root node to see exactly which variables are bound versus
unbound. Use this as corroborating evidence for token findings in Step 5 — a layer present
in get_design_context with a token reference but absent or unbound in get_variable_defs
confirms the token is not properly attached.
Call get_screenshot on the root node for visual context.
If the frame is large (> ~2000px tall), call get_design_context section by section using node IDs
from get_metadata to avoid truncation.
Step 2: Check Page Structure (type=page Only)
Inspect the top-level children of the root frame in the get_metadata output. The allowed
structure, in order, is:
- An optional
Header component instance (may be wrapped in a layer named Header)
- One or more
Section [Size] layers, each containing a Container [Size] child layer
- An optional
Footer component instance
- An optional
Modal component instance
Rules:
- Section and Container sizes do not need to match (
Section Small > Container Large is valid)
- Any unnamed or unrecognised top-level layer (e.g.
Frame 12) fails this check
- Skip entirely for
component and composition evaluations
Step 3: Check Variant Completeness (type=component Only)
For each property dimension visible in the component, run two checks.
Dictionary Enum Completeness
Fetch docs/DICTIONARIES.md
from GitHub to get the current list of Spirit dictionaries and their values.
If the component defines any value from a dictionary, it must define all values from that
dictionary. Flag any missing values as ⚠️.
If a property spans two dictionaries (e.g. a Color prop that combines ComponentButtonColor
and EmotionColor), check completeness against both.
Interaction State Completeness
If the component has an Interaction State property, every other property combination must have
all of: Default, Hover, Focus, Active, Disabled. Flag any combination that is missing
one or more states as ⚠️.
Step 4: Check DS Component Usage
For each visible UI element, verify it maps to an existing Spirit component:
| Check |
What to look for |
| Component instance |
Does the Figma layer have a Code Connect snippet? If not, it may be a custom composition or a missing DS component. When no Code Connect is found, check whether the component exists in the codebase — the two cases are reported differently (see "Differentiating not-yet-Implemented From lacks-Code Connect" below). Exception: plain <text> layers are not components in Figma — Spirit uses text styles (Body, Heading, etc.) for typography, not typography components. Never flag a text layer as a missing DS component. Exception: layout layers named Container, Section, Stack, or Grid are intentional named frames — they are not DS component instances and must not be flagged as missing components. |
| Correct component |
Is the right DS component used? Note: there is no Link component in Figma — designers indicate links via text styles with link in the name (e.g. Body/Medium/Link Regular). This is correct design practice and must NOT be flagged as a finding. Instead, add a note to Development Considerations that these text layers should be implemented as the DS Link component in code. |
| No links inside interactive element labels |
A Link (or link-styled text) placed inside the label of a form field (e.g. TextField, Toggle, Checkbox, Radio, Select) is a 🚨 blocker — a link nested inside a <label> is invalid HTML and causes serious accessibility issues. Links in helper text or validation text are acceptable, as those are plain text nodes, not interactive elements. |
| No unimplemented component features |
Compare what the design shows for each component instance against its Code Connect output. If the design uses a variant, prop, or slot that is absent from the Code Connect snippet (e.g. a description text area on a Toggle that Code Connect never renders), the feature does not exist in the DS yet and must be added before the design can be implemented as shown. Routing: record it in Development Considerations with 🚨 severity and add a matching row to Required DS Changes. Do not include it in Findings — Code Connect gaps are developer/DS work, not designer-actionable. Specialised case — form-field Enhancer / Addon slots: for TextField, Select, and TextArea instances, inspect child layers inside the instance from get_metadata. If any child layer is named Enhancer, Leading, or Trailing, or is a visible icon/text node that does not appear in the Code Connect snippet, the Enhancer feature is in use. This maps to the Addon API planned for the next major version of Spirit; the current Code Connect snippet omits it. Route as ⚠️ in Development Considerations and add a Required DS Changes row (e.g. "Add Addon/Enhancer support to TextField"). |
| "NEW" suffix in layer name |
Layers named "XYZ NEW" signal a proposed new DS component that doesn't exist yet — flag as a required DS change. |
| Deprecated components |
Search for a DEPRECATIONS.md file in the repository (e.g. packages/web-react/DEPRECATIONS.md in Spirit). If not found locally, fetch it from packages/web-react/DEPRECATIONS.md on GitHub as a reference. Check whether any components in the design appear in that file. |
ControlButton nested inside Tag |
When the get_design_context output shows a ControlButton Code Connect snippet that is a descendant of a Tag instance layer, add a Development Considerations ℹ️ note: from Spirit v5, ControlButton automatically inherits the parent Tag's color scheme via data-spirit-color-scheme and applies the exact token set for that scheme. In the current version it uses dynamic-color-* CSS utility classes as a fallback, so the rendered colors may differ. This is informational — it is not designer-actionable and needs no Required DS Change. |
When a layer lacks a Code Connect snippet and its structure or name suggests it may correspond to
a Spirit component, try to suggest a replacement:
search_design_system (preferred, if available) — call with the layer's role or name
(e.g. "avatar", "stat tile", "navigation item"). Available in Codex/Cursor environments.
get_code_connect_suggestions (fallback) — call on the specific node. Available in the
Figma desktop app MCP.
Use whichever tool is available in the current environment; skip silently if neither is. Include
a replacement suggestion in the finding only when the match is credible. Omit a suggestion when
results are ambiguous or the match is implausible.
Differentiating "not yet Implemented" From "lacks Code Connect"
When a Figma instance has no Code Connect snippet, the root cause is one of two very different things:
- Component exists in code but has no Code Connect binding — the fix is to add a Code Connect
file (DS work only). Code Connect files use one of these patterns:
*.figma.tsx, *.figma.ts,
or *.figma.stories.tsx.
- Component does not exist in code at all — the Code Connect gap is a downstream symptom; the real work is implementing the component first.
To determine which case applies, search the codebase for the component name and for its Code
Connect file.
Resolve the components search path by trying the following in order, stopping at the first match:
packages/web-react/src/components/ — Spirit's default components path
libs/design-system/components/ — Cyborg convention
componentsPath value in .agents/skills/review-figma-design/config.json — custom override
(set componentsPath to a relative path from the repo root; null skips this step)
If none of the paths exist in the repository, skip the existence check and treat the component
as "not yet implemented".
Grep the resolved path for the component name:
grep -r "ComponentName" <resolved-path> --include="*.tsx" -l 2>/dev/null | head -1
When the component exists, look for its Code Connect file. Match all Code Connect file
patterns — a component may be bound from any of them, so a *.figma.tsx-only search reports
false "lacks Code Connect" gaps:
find <resolved-path> \
\( -name "*.figma.tsx" -o -name "*.figma.ts" -o -name "*.figma.stories.tsx" \) \
-path "*ComponentName*" 2>/dev/null | head -5
If the component's files are not laid out per-directory, fall back to matching the file name
instead of the path:
find <resolved-path> \
\( -name "ComponentName.figma.tsx" -o -name "ComponentName.figma.ts" \
-o -name "ComponentName.figma.stories.tsx" \) 2>/dev/null | head -5
Apply the result as follows:
| Step 2 (component) |
Step 3 (Code Connect file) |
Case |
Development Considerations wording |
Required DS Changes entry |
| Match found |
No match |
Exists in code, no Code Connect |
🚨 \ComponentName` lacks Code Connect — component is implemented (path: `packages/…`) but has no Figma Code Connect binding` |
"Add Code Connect for ComponentName" |
| Match found |
Match found |
Code Connect exists but is stale or unpublished |
⚠️ \ComponentName` has a Code Connect file (``) but Figma returns no snippet — the mapping is likely stale or unpublished` |
"Republish Code Connect for ComponentName" |
| No match |
— |
Not yet implemented |
🚨 \ComponentName` is not yet implemented — no code equivalent exists; implement from DS primitives, then add Code Connect` |
"Implement ComponentName" |
Product-specific components (names with a product prefix such as OPU-, Cyborg-, or similar) will not be added to the Spirit DS. For these:
- Keep the 🚨 severity (implementation is still blocked).
- Change the wording to reflect that it is a product-level concern: "product-specific component not in Spirit DS; implement in the product codebase using DS primitives, then add Code Connect there."
- Omit the Required DS Changes row (Spirit DS has no action to take).
Cross-Frame Instance Height Consistency (multi-Frame Mode Only)
After collecting get_metadata for all frames, build a name → heights map by recording the
height attribute of every <instance name="…"> element across all frames. For any named
instance that appears in more than one frame with differing height values, flag it in
Findings as ⚠️:
<name> has inconsistent heights across frames (Npx in "Frame A" vs Mpx in "Frame B"). Verify
all variants were built from the same master component and that the height difference is
intentional.
This is especially important for custom subcomponents that stack a label above a form field:
a small unintended height change silently misaligns an adjacent Button and cannot be seen when
frames are reviewed in isolation.
Step 5: Check Token Usage
Inspect the get_design_context output for hardcoded CSS literals. These indicate missing token
references in the design.
All token references must use one of three valid prefixes: device, global, or themed.
Any var(--...) that does not start with one of these is invalid regardless of which DS is in use.
Spacing tokens:
- Hardcoded
gap-[Xpx], p-[Xpx], m-[Xpx] without a var(--global/spacing/...) reference → tokens missing; flag so the designer can attach the correct token.
Color tokens:
- Hardcoded hex/rgb values → tokens missing; flag as ⚠️.
- Token-referenced colors that do not start with
themed → wrong token scope; colors must use var(--themed/...) tokens; flag as ⚠️.
- Exception: hardcoded colors on
<vector> layers (SVG illustration paths) are expected and should NOT be flagged — illustrations embed colors by design and are not tokenised.
Typography:
- Verify text styles use
var(--device/typography/...) tokens.
- Custom font-weight/size combinations outside the scale should be flagged.
Radius / shadow:
- Verify
border-radius uses var(--global/radius/...) tokens.
- Verify
box-shadow uses var(--global/shadow/...) or var(--themed/shadow/...) tokens.
- Border-radius in sibling form-field + Button rows: when a
Button instance and a form-field component (TextField, Select, Picker, or a custom search-input subcomponent) appear as direct siblings in the same auto-layout frame, call get_variable_defs on both and compare their border-radius variable bindings. If they reference different --global/radius/... tokens, or one uses a token while the other uses a hardcoded value, flag it in Findings as ⚠️ — the designer can fix this by attaching the matching global/radius/... token to the outlying component.
Step 6: Check Icon Usage
For every icon in the design:
Resolve the icon search path by trying the following in order, stopping at the first match:
packages/icons/src/ — Spirit's default icon path
libs/design-icons/ — Cyborg convention
iconsPath value in .agents/skills/review-figma-design/config.json — custom override
(set iconsPath to a relative path from the repo root; null skips this step)
If none of the paths exist in the repository, skip the existence check and note in the report
that the icon path could not be resolved.
Verify the icon name exists in the resolved path:
Grep for the icon name in <resolved-path>
Verify it is using <Icon name="..." /> via Code Connect — not a raw SVG or image asset.
Route failures by type:
- Icon name missing from the codebase — this is a joint designer + developer concern:
- Record in Findings with 🚨 severity — the designer should make sure the icon is
present in their Figma asset library and published so downstream consumers have access.
- Record in Development Considerations with 🚨 severity and add a matching row to
Required DS Changes (e.g.
Add missing icon "<name>") — the DS needs to ship the
icon in code before the design can be implemented.
- Raw SVG / image asset instead of
<Icon name="..." /> — this is a Code Connect
binding concern. Record in Development Considerations with 🚨 severity and, if a DS
fix is needed, add a row to Required DS Changes. Do not include it in Findings.
Step 7: Spirit Repo Checks (Spirit Repo Only)
Check whether the skill is running inside the Spirit repo by testing for the presence of
packages/web-react/. If the directory does not exist, skip this step entirely — all JIRA cells
in the Required DS Changes table will be to be created.
If running in Spirit, perform the following:
Cross-reference git history — run git log --oneline -50 and scan recent commit messages
for DS- ticket references (e.g. #DS-2300). If a commit relates to a component or feature
visible in the design (e.g. a recently shipped prop, a new component, a Code Connect update),
note the ticket number and annotate the relevant finding — either to flag it as already in
progress or to explain why something appears incomplete.
Populate JIRA column — for each row in the Required DS Changes table, check whether a
matching DS- ticket was found in the git log. If yes, link it as
[DS-XXXX](https://jira.almacareer.tech/browse/DS-XXXX). If no matching ticket was found,
write to be created.
Step 8: Identify Required DS Changes
Based on your findings, list any changes needed in the DS itself:
- New components (e.g., "File Upload NEW" → new
FileUpload component needed) — always a 🚨 blocker, as development cannot proceed without a DS component or an agreed implementation path
- Component updates (e.g., a prop variant that doesn't exist yet)
- Token additions (e.g., a spacing value not in the Spirit scale)
- Code Connect updates (e.g., a recently shipped feature not yet reflected in Code Connect)
- Missing icons (e.g., a name referenced in the design that is not present in
packages/icons/src/)
Required DS Changes is the canonical work-item list for implementation issues. Every
severity-tagged item recorded in Development Considerations that requires DS work must have a
matching row here.
Estimate effort for each: Low (< 2h), **Medium** (2–8h), **High** (> 8h).
Step 9: Compile Design Evaluation
Based on all findings from Steps 2–8, compile the generic checks table. Each row either passes (✅)
or fails (❌). This gives an at-a-glance view of what's correct as well as what isn't.
| Check |
What to verify |
| Top-level frame uses Section/Container structure (page only) |
See Step 2 for the full structure rules. Omit this row for component and composition. |
| Design tokens used for all values |
No hardcoded px, hex, or rgb values — spacing, color, radius, shadow all use var(--...) tokens |
| DS components used for all UI elements |
Every UI element maps to a DS component instance (<instance> in metadata, Code Connect present in design context); no custom compositions where a DS component exists |
| No detached or modified components |
Detached instances appear as <frame> in metadata instead of <instance> — flag any <frame> whose name matches a known DS component. Exception: <frame> layers named Container, Section, Stack, or Grid are intentional named frames — skip these. Modified instances cannot be reliably detected via the Figma MCP; always show ❓ for the modified part and add a note below the table. |
| No unimplemented component features |
Every variant, prop, or slot visible in the design for each component instance appears in its Code Connect output; anything absent from Code Connect is not yet implemented in the DS |
| No deprecated components |
No deprecated components or props used |
| Icons from DS icon set |
All icons use <Icon name="..." /> with a name that exists in the resolved icon path (see Step 6) |
Specific findings do NOT go in this table — they are routed by audience per Steps 4 and 6:
- Designer-actionable (wrong component choice, missing token in a particular spot, detached
component, deprecated component used, missing icon in the designer's asset library, etc.) →
Findings (see Step 11).
- Implementation-actionable (Code Connect gaps, unimplemented component features, icon name
missing from the codebase, raw SVG used instead of the
Icon component, etc.) →
Development Considerations with severity and, where DS work is required, a row in
Required DS Changes.
See Step 11 for the detailed routing and format.
Step 10: Capture Per-Finding Screenshots
Derive the output names:
Slugification rule (used throughout): lowercase the input, replace whitespace, hyphens,
pipes, and other non-alphanumeric characters with hyphens, collapse consecutive hyphens,
trim leading/trailing hyphens.
- Single-frame mode:
<topic-slug> = slugified frame name, optionally prefixed with [issue-id]-.
Example: DS-2475-user-account-settings
- Multi-frame mode (combined report):
<topic-slug> = slugified canvas/page name, optionally
prefixed with [issue-id]-. Example: DS-2475-reply-form
<report-name> = [issue-id]-design-review (with issue ID) or design-review (without).
- Output (both modes):
design-reviews/<topic-slug>/<report-name>.{md|html|pdf}
Run mkdir -p design-reviews/<topic-slug> to create the output folder.
For each finding that references a specific node ID (not just the root frame), call
get_screenshot with that node ID. The tool zooms to the node bounds automatically — no manual
crop needed. Do NOT capture a screenshot for findings that only reference the root frame (already
captured in Step 1).
Save each screenshot to design-reviews/<topic-slug>/ as finding-{n}.png, where {n}
matches the finding's number in the report (🚨 first, then ⚠️, then ℹ️, matching sort order).
In multi-frame mode findings are numbered sequentially across all frames.
The Figma MCP server returns images as base64 PNG — decode and save each one using the
save-screenshots.js script in this skill's scripts/ directory. Each argument is a NODE_ID:PATH pair
(the script splits on the last colon, so node IDs like 2802:66561 work correctly):
node .agents/skills/review-figma-design/scripts/save-screenshots.js \
"NODE_ID_1:design-reviews/SLUG/finding-1.png" \
"NODE_ID_2:design-reviews/SLUG/finding-2.png"
Replace NODE_ID_* and SLUG with the actual values before running.
Keep a numbered index (finding → screenshot filename) to use during report writing.
If a finding spans multiple nodes and warrants more than one screenshot, use letters:
finding-1a.png, finding-1b.png, finding-1c.png, …
Save the overview and compute pin positions for the HTML report:
a. Decode and save the root screenshot to <output-folder>/overview.png using the same
save-screenshots.js script (add an entry with the root node ID and path
<output-folder>/overview.png). Capture the overview's natural dimensions from the
script's stdout — the line has the form Saved <path> (<width>x<height>). They are
needed in step 7d below to size the overview box on the HTML cover.
b. For each finding, compute its absolute position within the root frame by walking the
metadata tree from that node up to the root frame and summing the x/y offsets of every
ancestor (not including the root frame itself). This means building a parent map from the
XML tree and following it all the way up — not just adding one parent's offset. For deeply
nested nodes (e.g. a text layer inside a component inside a frame inside a section) every
intermediate x/y must be accumulated; skipping levels produces pins that cluster
incorrectly. Record:
center_x = abs_x + width / 2
center_y = abs_y + height / 2
pin_left = round(center_x / canvas_w * 100, 1) — percentage of canvas width
pin_top = round(center_y / canvas_h * 100, 1) — percentage of canvas height
Store (n, pin_left, pin_top) for each finding — these become the style values for the
pin <a> elements in the HTML cover page (style="left:XX.X%;top:YY.Y%").
c. The canvas dimensions used for pin percentages (canvas_w, canvas_h) must come from
the root frame's bounding box in get_metadata, not the overview PNG. The Figma
screenshot may be rendered at a different resolution than the frame's logical coordinates,
but the positions of findings are in logical units — matching the root frame is what keeps
pins aligned with features in the image.
d. Build the overview wrapper's inline style using the PNG dimensions captured in step 7a.
The HTML cover sizes the overview with aspect-ratio, so the wrapper needs the image's
natural ratio as a CSS custom property:
style="--overview-aspect: <width>/<height>;"</height></width>
(note the leading space — the placeholder sits where an HTML attribute would). Substitute
this into {{OVERVIEW_WRAP_STYLE}} when writing the HTML. Without it, the overview box
falls back to an unconstrained aspect and the image will not fit the cover page correctly.
Step 11: Write the Report
Output the complete report to the conversation first — the user reads it here. Then write the
identical content to design-reviews/<topic-slug>/<report-name>.md immediately, without asking for
confirmation. Both happen together; neither requires user approval.
Produce a structured Markdown report with the sections below. Use the same tone and format as the
team's existing reports — concise, developer-facing,
…(truncated)
1---2name: spirit-review-figma-design3description: Review a Figma design from a Spirit Design System perspective before handoff to development. Use when a designer asks for a DS review, a design needs to be checked for DS compliance, or a handoff review is required. Requires the Figma MCP to be available.4---56# Spirit Design System — Review Figma Design78This skill guides you through a structured design review from a Spirit Design System perspective,9matching the team's handoff process. The output is a written report (for the ticket) and, if issues10are found, a list of Figma comments to be added.1112---1314## Invocation1516```text17/spirit:review-figma-design [type] [issue-id] [figma-url] [--post-comments-to-figma]18```1920All arguments are optional and positionally flexible — each is unambiguous by its format:21`type` is one of `page`, `component`, `composition`; `issue-id` matches `[A-Z]+-\d+` (e.g.22`DS-2475`); `figma-url` starts with `https://`; `--post-comments-to-figma` is a literal flag.2324**`[type]`** — When omitted, auto-detected from the frame structure (see Step 1); falls back to25`composition` if detection is inconclusive. `page` is never auto-detected — pass it explicitly26when reviewing a full page.2728| Type | When to use |29| ------------- | ----------------------------------------------------------------- |30| `page` | Full page design — includes the Section/Container structure check |31| `component` | A single DS component being designed or updated |32| `composition` | A multi-component composition that is not a full page |3334**`[issue-id]`** — optional JIRA issue ID (e.g. `DS-2475`). When provided, it is prepended to the35output directory name: `design-reviews/DS-2475-reply-form/`. When omitted, the directory uses only36the frame-name slug.3738**`[figma-url]`** — optional Figma frame URL. Two behaviours:3940- **Omitted** — review the frame currently selected in the Figma desktop app. Call `get_metadata`41 without a `nodeId`.42- **Provided** — extract the `nodeId` from the URL and use it for all Figma MCP calls. The node ID43 is the `node-id` query parameter with `-` replaced by `:` (e.g. `node-id=34675-59177` →44 `nodeId: "34675:59177"`).4546**`[--post-comments-to-figma]`** — optional flag. When present, all proposed Figma comments are47posted automatically at the end of the review without asking for confirmation (see Step 13).4849Checks that apply only to specific evaluation types are marked accordingly throughout this skill.5051---5253## Prerequisites5455- The **Figma desktop app must be open** — it exposes the Figma MCP server.56- **Without a URL** — the target frame must be selected in the Figma desktop app.57- **With a URL** — no frame selection is needed; the URL identifies the target node.58- You must have access to the **Spirit component codebase** (for cross-referencing component existence).5960---6162## Workflow6364### Step 1: Fetch Design Data65661. Determine the target node:67 - **No URL provided** — call `get_metadata` without a `nodeId` (uses current Figma selection).68 If the call fails with an error such as "fileKey is required", the MCP server in this69 environment needs a file key. Stop and ask the user to provide a Figma URL, then proceed70 with the URL-based path below.71 - **URL provided** — extract the `nodeId` from the URL (see Invocation section) and pass it to72 all subsequent Figma MCP calls. If the URL has no `node-id` parameter, call `get_metadata`73 without a `nodeId` as well.74752. **Detect multi-frame mode** — inspect the root node returned by `get_metadata`:76 - If the root is a page or canvas (its direct children are `<frame>` elements rather than77 component layers), the URL points to a zoomed area or page rather than a single frame.78 - List the frames numbered and ask:79 > This URL contains **N frames**:80 >81 > 1. Frame One82 > 2. Frame Two83 > …84 > Review all N frames, or pick one? Enter a number to pick a single frame, or `all`.85 - Wait for the user's answer before continuing.86 - **User picks a number** — review only that frame in single-frame mode (continue with87 step 3 below, using the picked frame's node ID as the target).88 - **User answers `all`** — review all frames together and produce a **single combined report**.89 Run Steps 3–8 for each `<frame>` child in order (collecting findings across all frames), then90 write one report (Steps 9–11) that covers all frames. See Step 9 for the output path and91 Step 10 for the Frames Reviewed section format.92 - **Single-frame mode** (the normal case): the root node is already a single frame — continue93 with step 3 below.94953. If `[type]` was **not** provided, auto-detect it from the `get_metadata` output:96 - Inspect the direct children of the root frame.97 - If **all** direct children are `<symbol>` elements with names in `Property=Value` format98 (e.g. `Color=Primary, Size=Medium`), set type to `component`.99 - Otherwise, set type to `composition`.100 - Log the detected type so the user can see which was chosen.101 - If `[type]` **was** provided, use it as-is — skip detection entirely.1021034. Call `get_design_context` on the root node to extract:104 - All component instances and their Code Connect snippets105 - All token references (spacing, color, typography, radius, shadow)106 - Raw hardcoded values (CSS literals without `var(--...)`)107 - Any custom/raw HTML compositions that lack Code Connect mappings1085. Call `get_variable_defs` on the root node to see exactly which variables are bound versus109 unbound. Use this as corroborating evidence for token findings in Step 5 — a layer present110 in `get_design_context` with a token reference but absent or unbound in `get_variable_defs`111 confirms the token is not properly attached.1126. Call `get_screenshot` on the root node for visual context.113114If the frame is large (> ~2000px tall), call `get_design_context` section by section using node IDs115from `get_metadata` to avoid truncation.116117### Step 2: Check Page Structure (type=page Only)118119Inspect the top-level children of the root frame in the `get_metadata` output. The allowed120structure, in order, is:1211221. An optional `Header` component instance (may be wrapped in a layer named `Header`)1232. One or more `Section [Size]` layers, each containing a `Container [Size]` child layer1243. An optional `Footer` component instance1254. An optional `Modal` component instance126127Rules:128129- Section and Container sizes do not need to match (`Section Small` > `Container Large` is valid)130- Any unnamed or unrecognised top-level layer (e.g. `Frame 12`) fails this check131- Skip entirely for `component` and `composition` evaluations132133### Step 3: Check Variant Completeness (type=component Only)134135For each property dimension visible in the component, run two checks.136137#### Dictionary Enum Completeness138139Fetch [`docs/DICTIONARIES.md`](https://github.com/alma-oss/spirit-design-system/blob/main/docs/DICTIONARIES.md)140from GitHub to get the current list of Spirit dictionaries and their values.141If the component defines **any** value from a dictionary, it must define **all** values from that142dictionary. Flag any missing values as ⚠️.143144If a property spans two dictionaries (e.g. a `Color` prop that combines `ComponentButtonColor`145and `EmotionColor`), check completeness against both.146147#### Interaction State Completeness148149If the component has an `Interaction State` property, every other property combination must have150all of: `Default`, `Hover`, `Focus`, `Active`, `Disabled`. Flag any combination that is missing151one or more states as ⚠️.152153### Step 4: Check DS Component Usage154155For each visible UI element, verify it maps to an existing Spirit component:156157| Check | What to look for |158| ---------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |159| **Component instance** | Does the Figma layer have a Code Connect snippet? If not, it may be a custom composition or a missing DS component. When no Code Connect is found, check whether the component exists in the codebase — the two cases are reported differently (see "Differentiating not-yet-Implemented From lacks-Code Connect" below). **Exception:** plain `<text>` layers are not components in Figma — Spirit uses text styles (Body, Heading, etc.) for typography, not typography components. Never flag a text layer as a missing DS component. **Exception:** layout layers named `Container`, `Section`, `Stack`, or `Grid` are intentional named frames — they are not DS component instances and must not be flagged as missing components. |160| **Correct component** | Is the right DS component used? **Note:** there is no `Link` component in Figma — designers indicate links via text styles with `link` in the name (e.g. `Body/Medium/Link Regular`). This is correct design practice and must NOT be flagged as a finding. Instead, add a note to **Development Considerations** that these text layers should be implemented as the DS `Link` component in code. |161| **No links inside interactive element labels** | A `Link` (or link-styled text) placed inside the **label** of a form field (e.g. `TextField`, `Toggle`, `Checkbox`, `Radio`, `Select`) is a 🚨 blocker — a link nested inside a `<label>` is invalid HTML and causes serious accessibility issues. Links in **helper text** or **validation text** are acceptable, as those are plain text nodes, not interactive elements. |162| **No unimplemented component features** | Compare what the design shows for each component instance against its Code Connect output. If the design uses a variant, prop, or slot that is absent from the Code Connect snippet (e.g. a `description` text area on a `Toggle` that Code Connect never renders), the feature does not exist in the DS yet and must be added before the design can be implemented as shown. **Routing:** record it in **Development Considerations** with 🚨 severity **and** add a matching row to **Required DS Changes**. Do **not** include it in Findings — Code Connect gaps are developer/DS work, not designer-actionable. **Specialised case — form-field Enhancer / Addon slots:** for `TextField`, `Select`, and `TextArea` instances, inspect child layers inside the instance from `get_metadata`. If any child layer is named `Enhancer`, `Leading`, or `Trailing`, or is a visible icon/text node that does not appear in the Code Connect snippet, the Enhancer feature is in use. This maps to the Addon API planned for the next major version of Spirit; the current Code Connect snippet omits it. Route as ⚠️ in **Development Considerations** and add a **Required DS Changes** row (e.g. "Add Addon/Enhancer support to `TextField`"). |163| **"NEW" suffix in layer name** | Layers named "XYZ NEW" signal a proposed new DS component that doesn't exist yet — flag as a required DS change. |164| **Deprecated components** | Search for a `DEPRECATIONS.md` file in the repository (e.g. `packages/web-react/DEPRECATIONS.md` in Spirit). If not found locally, fetch it from [`packages/web-react/DEPRECATIONS.md`](https://raw.githubusercontent.com/alma-oss/spirit-design-system/refs/heads/main/packages/web-react/DEPRECATIONS.md) on GitHub as a reference. Check whether any components in the design appear in that file. |165| **`ControlButton` nested inside `Tag`** | When the `get_design_context` output shows a `ControlButton` Code Connect snippet that is a descendant of a `Tag` instance layer, add a **Development Considerations** ℹ️ note: from Spirit v5, `ControlButton` automatically inherits the parent `Tag`'s color scheme via `data-spirit-color-scheme` and applies the exact token set for that scheme. In the current version it uses `dynamic-color-*` CSS utility classes as a fallback, so the rendered colors may differ. This is informational — it is not designer-actionable and needs no Required DS Change. |166167When a layer lacks a Code Connect snippet and its structure or name suggests it may correspond to168a Spirit component, try to suggest a replacement:1691701. **`search_design_system`** _(preferred, if available)_ — call with the layer's role or name171 (e.g. `"avatar"`, `"stat tile"`, `"navigation item"`). Available in Codex/Cursor environments.1722. **`get_code_connect_suggestions`** _(fallback)_ — call on the specific node. Available in the173 Figma desktop app MCP.174175Use whichever tool is available in the current environment; skip silently if neither is. Include176a replacement suggestion in the finding only when the match is credible. Omit a suggestion when177results are ambiguous or the match is implausible.178179#### Differentiating "not yet Implemented" From "lacks Code Connect"180181When a Figma instance has no Code Connect snippet, the root cause is one of two very different things:182183- **Component exists in code but has no Code Connect binding** — the fix is to add a Code Connect184 file (DS work only). Code Connect files use one of these patterns: `*.figma.tsx`, `*.figma.ts`,185 or `*.figma.stories.tsx`.186- **Component does not exist in code at all** — the Code Connect gap is a downstream symptom; the real work is implementing the component first.187188To determine which case applies, search the codebase for the component name and for its Code189Connect file.1901911. Resolve the components search path by trying the following in order, stopping at the first match:192 1. `packages/web-react/src/components/` — Spirit's default components path193 2. `libs/design-system/components/` — Cyborg convention194 3. `componentsPath` value in `.agents/skills/review-figma-design/config.json` — custom override195 (set `componentsPath` to a relative path from the repo root; `null` skips this step)196197 If none of the paths exist in the repository, skip the existence check and treat the component198 as "not yet implemented".1992002. Grep the resolved path for the component name:201202 ```bash203 grep -r "ComponentName" <resolved-path> --include="*.tsx" -l 2>/dev/null | head -1204 ```2052063. When the component exists, look for its Code Connect file. Match **all** Code Connect file207 patterns — a component may be bound from any of them, so a `*.figma.tsx`-only search reports208 false "lacks Code Connect" gaps:209210 ```bash211 find <resolved-path> \212 \( -name "*.figma.tsx" -o -name "*.figma.ts" -o -name "*.figma.stories.tsx" \) \213 -path "*ComponentName*" 2>/dev/null | head -5214 ```215216 If the component's files are not laid out per-directory, fall back to matching the file name217 instead of the path:218219 ```bash220 find <resolved-path> \221 \( -name "ComponentName.figma.tsx" -o -name "ComponentName.figma.ts" \222 -o -name "ComponentName.figma.stories.tsx" \) 2>/dev/null | head -5223 ```224225Apply the result as follows:226227| Step 2 (component) | Step 3 (Code Connect file) | Case | Development Considerations wording | Required DS Changes entry |228| ------------------ | -------------------------- | ----------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------- |229| Match found | No match | Exists in code, no Code Connect | `🚨 \`ComponentName\` lacks Code Connect — component is implemented (path: \`packages/…\`) but has no Figma Code Connect binding` | "Add Code Connect for `ComponentName`" |230| Match found | Match found | Code Connect exists but is stale or unpublished | `⚠️ \`ComponentName\` has a Code Connect file (\`<matched path>\`) but Figma returns no snippet — the mapping is likely stale or unpublished` | "Republish Code Connect for `ComponentName`" |231| No match | — | Not yet implemented | `🚨 \`ComponentName\` is not yet implemented — no code equivalent exists; implement from DS primitives, then add Code Connect` | "Implement `ComponentName`" |232233**Product-specific components** (names with a product prefix such as `OPU-`, `Cyborg-`, or similar) will not be added to the Spirit DS. For these:234235- Keep the 🚨 severity (implementation is still blocked).236- Change the wording to reflect that it is a product-level concern: "product-specific component not in Spirit DS; implement in the product codebase using DS primitives, then add Code Connect there."237- Omit the Required DS Changes row (Spirit DS has no action to take).238239#### Cross-Frame Instance Height Consistency (multi-Frame Mode Only)240241After collecting `get_metadata` for all frames, build a name → heights map by recording the242`height` attribute of every `<instance name="…">` element across all frames. For any named243instance that appears in more than one frame with differing `height` values, flag it in244**Findings** as ⚠️:245246> `<name>` has inconsistent heights across frames (Npx in "Frame A" vs Mpx in "Frame B"). Verify247> all variants were built from the same master component and that the height difference is248> intentional.249250This is especially important for custom subcomponents that stack a label above a form field:251a small unintended height change silently misaligns an adjacent Button and cannot be seen when252frames are reviewed in isolation.253254### Step 5: Check Token Usage255256Inspect the `get_design_context` output for hardcoded CSS literals. These indicate missing token257references in the design.258259All token references must use one of three valid prefixes: `device`, `global`, or `themed`.260Any `var(--...)` that does not start with one of these is invalid regardless of which DS is in use.261262**Spacing tokens:**263264- Hardcoded `gap-[Xpx]`, `p-[Xpx]`, `m-[Xpx]` without a `var(--global/spacing/...)` reference → tokens missing; flag so the designer can attach the correct token.265266**Color tokens:**267268- Hardcoded hex/rgb values → tokens missing; flag as ⚠️.269- Token-referenced colors that do **not** start with `themed` → wrong token scope; colors must use `var(--themed/...)` tokens; flag as ⚠️.270- **Exception:** hardcoded colors on `<vector>` layers (SVG illustration paths) are expected and should NOT be flagged — illustrations embed colors by design and are not tokenised.271272**Typography:**273274- Verify text styles use `var(--device/typography/...)` tokens.275- Custom font-weight/size combinations outside the scale should be flagged.276277**Radius / shadow:**278279- Verify `border-radius` uses `var(--global/radius/...)` tokens.280- Verify `box-shadow` uses `var(--global/shadow/...)` or `var(--themed/shadow/...)` tokens.281- **Border-radius in sibling form-field + Button rows:** when a `Button` instance and a form-field component (`TextField`, `Select`, `Picker`, or a custom search-input subcomponent) appear as direct siblings in the same auto-layout frame, call `get_variable_defs` on both and compare their `border-radius` variable bindings. If they reference different `--global/radius/...` tokens, or one uses a token while the other uses a hardcoded value, flag it in **Findings** as ⚠️ — the designer can fix this by attaching the matching `global/radius/...` token to the outlying component.282283### Step 6: Check Icon Usage284285For every icon in the design:2862871. Resolve the icon search path by trying the following in order, stopping at the first match:288 1. `packages/icons/src/` — Spirit's default icon path289 2. `libs/design-icons/` — Cyborg convention290 3. `iconsPath` value in `.agents/skills/review-figma-design/config.json` — custom override291 (set `iconsPath` to a relative path from the repo root; `null` skips this step)292293 If none of the paths exist in the repository, skip the existence check and note in the report294 that the icon path could not be resolved.2952962. Verify the icon name exists in the resolved path:297 ```text298 Grep for the icon name in <resolved-path>299 ```3003. Verify it is using `<Icon name="..." />` via Code Connect — not a raw SVG or image asset.3014. Route failures by type:302 - **Icon name missing from the codebase** — this is a joint designer + developer concern:303 - Record in **Findings** with 🚨 severity — the designer should make sure the icon is304 present in their Figma asset library and published so downstream consumers have access.305 - Record in **Development Considerations** with 🚨 severity **and** add a matching row to306 **Required DS Changes** (e.g. `Add missing icon "<name>"`) — the DS needs to ship the307 icon in code before the design can be implemented.308 - **Raw SVG / image asset instead of `<Icon name="..." />`** — this is a Code Connect309 binding concern. Record in **Development Considerations** with 🚨 severity and, if a DS310 fix is needed, add a row to **Required DS Changes**. Do **not** include it in Findings.311312### Step 7: Spirit Repo Checks (Spirit Repo Only)313314Check whether the skill is running inside the Spirit repo by testing for the presence of315`packages/web-react/`. If the directory does not exist, skip this step entirely — all JIRA cells316in the Required DS Changes table will be _to be created_.317318If running in Spirit, perform the following:3193201. **Cross-reference git history** — run `git log --oneline -50` and scan recent commit messages321 for `DS-` ticket references (e.g. `#DS-2300`). If a commit relates to a component or feature322 visible in the design (e.g. a recently shipped prop, a new component, a Code Connect update),323 note the ticket number and annotate the relevant finding — either to flag it as already in324 progress or to explain why something appears incomplete.3253262. **Populate JIRA column** — for each row in the Required DS Changes table, check whether a327 matching `DS-` ticket was found in the git log. If yes, link it as328 `[DS-XXXX](https://jira.almacareer.tech/browse/DS-XXXX)`. If no matching ticket was found,329 write _to be created_.330331### Step 8: Identify Required DS Changes332333Based on your findings, list any changes needed in the DS itself:334335- New components (e.g., "File Upload NEW" → new `FileUpload` component needed) — always a 🚨 blocker, as development cannot proceed without a DS component or an agreed implementation path336- Component updates (e.g., a prop variant that doesn't exist yet)337- Token additions (e.g., a spacing value not in the Spirit scale)338- Code Connect updates (e.g., a recently shipped feature not yet reflected in Code Connect)339- Missing icons (e.g., a name referenced in the design that is not present in `packages/icons/src/`)340341Required DS Changes is the canonical work-item list for implementation issues. Every342severity-tagged item recorded in **Development Considerations** that requires DS work must have a343matching row here.344345Estimate effort for each: **Low** (< 2h), **Medium** (2–8h), **High** (> 8h).346347### Step 9: Compile Design Evaluation348349Based on all findings from Steps 2–8, compile the generic checks table. Each row either passes (✅)350or fails (❌). This gives an at-a-glance view of what's correct as well as what isn't.351352| Check | What to verify |353| -------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |354| Top-level frame uses Section/Container structure _(page only)_ | See Step 2 for the full structure rules. Omit this row for `component` and `composition`. |355| Design tokens used for all values | No hardcoded `px`, hex, or rgb values — spacing, color, radius, shadow all use `var(--...)` tokens |356| DS components used for all UI elements | Every UI element maps to a DS component instance (`<instance>` in metadata, Code Connect present in design context); no custom compositions where a DS component exists |357| No detached or modified components | Detached instances appear as `<frame>` in metadata instead of `<instance>` — flag any `<frame>` whose name matches a known DS component. **Exception:** `<frame>` layers named `Container`, `Section`, `Stack`, or `Grid` are intentional named frames — skip these. Modified instances cannot be reliably detected via the Figma MCP; always show ❓ for the modified part and add a note below the table. |358| No unimplemented component features | Every variant, prop, or slot visible in the design for each component instance appears in its Code Connect output; anything absent from Code Connect is not yet implemented in the DS |359| No deprecated components | No deprecated components or props used |360| Icons from DS icon set | All icons use `<Icon name="..." />` with a name that exists in the resolved icon path (see Step 6) |361362Specific findings do NOT go in this table — they are routed by audience per Steps 4 and 6:363364- **Designer-actionable** (wrong component choice, missing token in a particular spot, detached365 component, deprecated component used, missing icon in the designer's asset library, etc.) →366 **Findings** (see Step 11).367- **Implementation-actionable** (Code Connect gaps, unimplemented component features, icon name368 missing from the codebase, raw SVG used instead of the `Icon` component, etc.) →369 **Development Considerations** with severity and, where DS work is required, a row in370 **Required DS Changes**.371372See Step 11 for the detailed routing and format.373374### Step 10: Capture Per-Finding Screenshots3753761. Derive the output names:377378 **Slugification rule** (used throughout): lowercase the input, replace whitespace, hyphens,379 pipes, and other non-alphanumeric characters with hyphens, collapse consecutive hyphens,380 trim leading/trailing hyphens.381 - **Single-frame mode:** `<topic-slug>` = slugified frame name, optionally prefixed with `[issue-id]-`.382 Example: `DS-2475-user-account-settings`383 - **Multi-frame mode (combined report):** `<topic-slug>` = slugified canvas/page name, optionally384 prefixed with `[issue-id]-`. Example: `DS-2475-reply-form`385 - `<report-name>` = `[issue-id]-design-review` (with issue ID) or `design-review` (without).386 - Output (both modes): `design-reviews/<topic-slug>/<report-name>.{md|html|pdf}`3873882. Run `mkdir -p design-reviews/<topic-slug>` to create the output folder.3893. For each finding that references a specific node ID (not just the root frame), call390 `get_screenshot` with that node ID. The tool zooms to the node bounds automatically — no manual391 crop needed. Do NOT capture a screenshot for findings that only reference the root frame (already392 captured in Step 1).3934. Save each screenshot to `design-reviews/<topic-slug>/` as `finding-{n}.png`, where `{n}`394 matches the finding's number in the report (🚨 first, then ⚠️, then ℹ️, matching sort order).395 In multi-frame mode findings are numbered sequentially across all frames.396 The Figma MCP server returns images as base64 PNG — decode and save each one using the397 `save-screenshots.js` script in this skill's `scripts/` directory. Each argument is a `NODE_ID:PATH` pair398 (the script splits on the last colon, so node IDs like `2802:66561` work correctly):399400 ```bash401 node .agents/skills/review-figma-design/scripts/save-screenshots.js \402 "NODE_ID_1:design-reviews/SLUG/finding-1.png" \403 "NODE_ID_2:design-reviews/SLUG/finding-2.png"404 ```405406 Replace `NODE_ID_*` and `SLUG` with the actual values before running.4074085. Keep a numbered index (finding → screenshot filename) to use during report writing.4096. If a finding spans multiple nodes and warrants more than one screenshot, use letters:410 `finding-1a.png`, `finding-1b.png`, `finding-1c.png`, …4114127. Save the overview and compute pin positions for the HTML report:413414 a. Decode and save the root screenshot to `<output-folder>/overview.png` using the same415 `save-screenshots.js` script (add an entry with the root node ID and path416 `<output-folder>/overview.png`). **Capture the overview's natural dimensions from the417 script's stdout** — the line has the form `Saved <path> (<width>x<height>)`. They are418 needed in step 7d below to size the overview box on the HTML cover.419420 b. For each finding, compute its **absolute position within the root frame** by walking the421 metadata tree from that node up to the root frame and summing the `x`/`y` offsets of **every**422 ancestor (not including the root frame itself). This means building a parent map from the423 XML tree and following it all the way up — not just adding one parent's offset. For deeply424 nested nodes (e.g. a text layer inside a component inside a frame inside a section) every425 intermediate `x`/`y` must be accumulated; skipping levels produces pins that cluster426 incorrectly. Record:427 - `center_x = abs_x + width / 2`428 - `center_y = abs_y + height / 2`429 - `pin_left = round(center_x / canvas_w * 100, 1)` — percentage of canvas width430 - `pin_top = round(center_y / canvas_h * 100, 1)` — percentage of canvas height431432 Store `(n, pin_left, pin_top)` for each finding — these become the `style` values for the433 pin `<a>` elements in the HTML cover page (`style="left:XX.X%;top:YY.Y%"`).434435 c. The canvas dimensions used for pin percentages (`canvas_w`, `canvas_h`) must come from436 the **root frame's bounding box** in `get_metadata`, not the overview PNG. The Figma437 screenshot may be rendered at a different resolution than the frame's logical coordinates,438 but the positions of findings are in logical units — matching the root frame is what keeps439 pins aligned with features in the image.440441 d. Build the overview wrapper's inline style using the PNG dimensions captured in step 7a.442 The HTML cover sizes the overview with `aspect-ratio`, so the wrapper needs the image's443 natural ratio as a CSS custom property:444445 ```html446 style="--overview-aspect: <width>/<height>;"</height></width>447 ```448449 (note the leading space — the placeholder sits where an HTML attribute would). Substitute450 this into `{{OVERVIEW_WRAP_STYLE}}` when writing the HTML. Without it, the overview box451 falls back to an unconstrained aspect and the image will not fit the cover page correctly.452453### Step 11: Write the Report454455Output the complete report to the conversation first — the user reads it here. Then write the456identical content to `design-reviews/<topic-slug>/<report-name>.md` immediately, without asking for457confirmation. Both happen together; neither requires user approval.458459Produce a structured Markdown report with the sections below. Use the same tone and format as the460team's existing reports — concise, developer-facing,461462…(truncated)