# Spirit:review Figma Design

> 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.

- Skill: `alma-oss/spirit-review-figma-design` (Agent Skill, multi-file: 6 files)
- Install (CLI): `npx skillmds@latest add alma-oss/spirit-review-figma-design`
- Raw SKILL.md: https://api.skillmd.com/api/skills/alma-oss/spirit-review-figma-design/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Design & Media
- Author: alma-oss (https://skillmd.com/u/alma-oss)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/alma-oss/spirit-review-figma-design

---


# 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

```text
/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

1. 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.

2. **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**:
     >
     > 1. Frame One
     > 2. 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.

3. 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.

4. 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
5. 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.
6. 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:

1. An optional `Header` component instance (may be wrapped in a layer named `Header`)
2. One or more `Section [Size]` layers, each containing a `Container [Size]` child layer
3. An optional `Footer` component instance
4. 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`](https://github.com/alma-oss/spirit-design-system/blob/main/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`](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.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                            |
| **`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:

1. **`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.
2. **`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.

1. Resolve the components search path by trying the following in order, stopping at the first match:
   1. `packages/web-react/src/components/` — Spirit's default components path
   2. `libs/design-system/components/` — Cyborg convention
   3. `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".

2. Grep the resolved path for the component name:

   ```bash
   grep -r "ComponentName" <resolved-path> --include="*.tsx" -l 2>/dev/null | head -1
   ```

3. 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:

   ```bash
   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:

   ```bash
   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 (\`<matched path>\`) 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:

1. Resolve the icon search path by trying the following in order, stopping at the first match:
   1. `packages/icons/src/` — Spirit's default icon path
   2. `libs/design-icons/` — Cyborg convention
   3. `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.

2. Verify the icon name exists in the resolved path:
   ```text
   Grep for the icon name in <resolved-path>
   ```
3. Verify it is using `<Icon name="..." />` via Code Connect — not a raw SVG or image asset.
4. 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:

1. **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.

2. **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

1. 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}`

2. Run `mkdir -p design-reviews/<topic-slug>` to create the output folder.
3. 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).
4. 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):

   ```bash
   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.

5. Keep a numbered index (finding → screenshot filename) to use during report writing.
6. If a finding spans multiple nodes and warrants more than one screenshot, use letters:
   `finding-1a.png`, `finding-1b.png`, `finding-1c.png`, …

7. 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:

   ```html
   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)
