# Figma Extract Rules Draft

> Draft only. Do not consume in scripts until plan owner reviews and renames to `figma-extract-rules.md`.
> Purpose: define which child nodes extract should recurse into, which visual data to capture, and how child evidence maps back to variant axes.

## Contract

- This file is the source of truth for child-node extraction rules.
- Extract must read this file; no `if componentName === "Slider"` script branches.
- Current draft examples are based on reduced raw component files plus code-side role names. Current `figma-data/raw/components/*.json` files do not preserve `children`, so all `nodeNameMatch` values below need full raw-cache confirmation.

## Component Schema

| Field | Type | Required | Notes |
|---|---|:-:|---|
| `figmaComponent` | string | yes | Published Figma component set name. |
| `figmaComponentSetIds` | string[] | yes | Figma component set node IDs. |
| `extractionMode` | `top-level-only` \| `recursive-with-rules` | yes | Default to `top-level-only` when no rule exists. |
| `maxTraversalDepth` | number | yes | Component-level traversal cap. |
| `childRoles` | array | yes | Role rules below. |

## Child Role Schema

| Field | Type | Required | Notes |
|---|---|:-:|---|
| `roleName` | enum | yes | Closed enum: `track`, `fill-bar`, `handle`, `icon`, `label`, `thumb`, `spinner`, `arrow`, `divider`, `background`, `container`, `unknown-visual`. |
| `nodeNameMatch` | regex or literal string | yes | Match local name and full node path. |
| `nodeTypeMatch` | string[] | no | Optional Figma node types. |
| `depthLimit` | number | yes | Per-role cap from variant root. |
| `captureKinds` | string[] | yes | `fills`, `strokes`, `text`, `dimensions`, `effects`, `vectorNodeIds`. |
| `axisAttribution` | enum | yes | `inherit-from-variant`, `pattern-match`, or `manual`. |
| `manualAxisMap` | object | no | Required only for `manual`. |
| `codeRoleHint` | string | no | Downstream selector/concept hint; not validation truth. |
| `evidenceSource` | string[] | yes | Rule path and confirmation source. |
| `reviewStatus` | enum | yes | `draft`, `needs-full-raw-cache-confirmation`, `approved`. |

## Extract Evidence Levels

| evidenceLevel | Meaning | Audit use |
|---|---|---|
| `rule-matched` | Child matched an approved rule. | Can satisfy audit. |
| `unmatched-but-captured` | Visible paint found without a rule. | Discovery only. |
| `placeholder-only` | Only invisible placeholder paint. | Ignore in audit. |

## Draft Instances

### Slider

- `figmaComponent`: `Slider`; `figmaComponentSetIds`: `[4934:7206]`; `extractionMode`: `recursive-with-rules`; `maxTraversalDepth`: `4`
- Current raw status: `slider__4934_7206.json` first variant has opacity-0 placeholder fill, text `60`, `vectorNodeIds: []`, and no `children`.

| roleName | nodeNameMatch | nodeTypeMatch | depthLimit | captureKinds | axisAttribution | codeRoleHint | reviewStatus |
|---|---|---|---:|---|---|---|---|
| `track` | `/track\|rail\|base/i` | `[FRAME, RECTANGLE, VECTOR]` | 4 | `[fills, strokes, dimensions]` | `inherit-from-variant` | `.slider__input`, `--slider-track-bg` | `needs-full-raw-cache-confirmation` |
| `fill-bar` | `/fill\|progress\|active/i` | `[FRAME, RECTANGLE, VECTOR]` | 4 | `[fills, dimensions]` | `inherit-from-variant` | `linear-gradient(...)`, `--slider-fill-color` | `needs-full-raw-cache-confirmation` |
| `handle` | `/handle\|thumb\|knob/i` | `[ELLIPSE, FRAME, VECTOR]` | 4 | `[fills, strokes, dimensions]` | `inherit-from-variant` | slider thumb pseudo elements | `needs-full-raw-cache-confirmation` |

### Rating

- `figmaComponent`: `Rating`; `figmaComponentSetIds`: `[4935:7294]`; `extractionMode`: `recursive-with-rules`; `maxTraversalDepth`: `4`
- Current raw status: `rating__4935_7294.json` first variant has opacity-0 placeholder fill, 5 vector node IDs, and no `children`.

| roleName | nodeNameMatch | nodeTypeMatch | depthLimit | captureKinds | axisAttribution | codeRoleHint | reviewStatus |
|---|---|---|---:|---|---|---|---|
| `icon` | `/star\|rating\|icon/i` | `[VECTOR, INSTANCE, FRAME]` | 4 | `[fills, strokes, dimensions, vectorNodeIds]` | `inherit-from-variant` | `.rating__icon`, `.rating__star--on`, `.rating-star--off` | `needs-full-raw-cache-confirmation` |

### Progress

- `figmaComponent`: `Progress`; `figmaComponentSetIds`: `[4915:7287]`; `extractionMode`: `recursive-with-rules`; `maxTraversalDepth`: `4`
- Current raw status: `progress__4915_7287.json` first variant has opacity-0 placeholder fill, text `60%`, and no `children`.

| roleName | nodeNameMatch | nodeTypeMatch | depthLimit | captureKinds | axisAttribution | codeRoleHint | reviewStatus |
|---|---|---|---:|---|---|---|---|
| `track` | `/track\|rail\|base/i` | `[FRAME, RECTANGLE, VECTOR]` | 4 | `[fills, dimensions]` | `inherit-from-variant` | `.progress__track`, `--progress-track-bg` | `needs-full-raw-cache-confirmation` |
| `fill-bar` | `/fill\|progress\|bar\|value/i` | `[FRAME, RECTANGLE, VECTOR]` | 4 | `[fills, dimensions]` | `inherit-from-variant` | `.progress__fill`, `--progress-fill-color` | `needs-full-raw-cache-confirmation` |

### Notification

- `figmaComponent`: `Notification`; `figmaComponentSetIds`: `[1408:17154]`; `extractionMode`: `recursive-with-rules`; `maxTraversalDepth`: `5`
- Current raw status: `notification__1408_17154.json` first variant has container fill/stroke/text, 5 vector node IDs, and no `children`.

| roleName | nodeNameMatch | nodeTypeMatch | depthLimit | captureKinds | axisAttribution | codeRoleHint | reviewStatus |
|---|---|---|---:|---|---|---|---|
| `icon` | `/icon\|warning\|error\|info\|close\|message/i` | `[VECTOR, INSTANCE, FRAME]` | 5 | `[fills, strokes, dimensions, vectorNodeIds]` | `inherit-from-variant` | `.notif__icon`, `.notif__close-icon` | `needs-full-raw-cache-confirmation` |

### Switch

- `figmaComponent`: `Switch`; `figmaComponentSetIds`: `[348:3483]`; `extractionMode`: `recursive-with-rules`; `maxTraversalDepth`: `4`
- Current raw status: `switch__348_3483.json` first variant has top-level track fill and no `children`; thumb/spinner data is absent.

| roleName | nodeNameMatch | nodeTypeMatch | depthLimit | captureKinds | axisAttribution | codeRoleHint | reviewStatus |
|---|---|---|---:|---|---|---|---|
| `thumb` | `/thumb\|handle\|knob\|circle/i` | `[ELLIPSE, FRAME, VECTOR]` | 4 | `[fills, strokes, dimensions]` | `inherit-from-variant` | `.switch-thumb` | `needs-full-raw-cache-confirmation` |
| `spinner` | `/loading\|spinner/i` | `[VECTOR, INSTANCE, FRAME]` | 4 | `[fills, strokes, dimensions, vectorNodeIds]` | `inherit-from-variant` | `.switch-spinner` | `needs-full-raw-cache-confirmation` |

## Evidence Sources For Draft

- `figma-data/raw/components/slider__4934_7206.json`
- `figma-data/raw/components/rating__4935_7294.json`
- `figma-data/raw/components/progress__4915_7287.json`
- `figma-data/raw/components/notification__1408_17154.json`
- `figma-data/raw/components/switch__348_3483.json`
- `src/components/Slider/Slider.vue`
- `src/components/Rating/Rating.vue`
- `src/components/Progress/Progress.vue`
- `src/components/Notification/Notification.vue`
- `src/components/Switch/Switch.vue`
