diff --git a/skills/pine-figma/README.md b/skills/pine-figma/README.md new file mode 100644 index 000000000..e2d0bce50 --- /dev/null +++ b/skills/pine-figma/README.md @@ -0,0 +1,60 @@ +# `pine-figma` — the design-side companion to the `pine` code skill + +This agent skill teaches a coding/design agent to **author Figma that actually +uses Pine** — composing from published component instances and binding every +value to a Pine variable or text style — instead of hand-drawing detached frames +full of hardcoded px that merely *look* like Pine. + +It is the mirror of the `pine` code skill: + +| Direction | Skill | The rule | +|---|---|---| +| Prompt → **code** | `pine` | Resolve every component/token/icon through the Pine MCP; gate on the layout validators. | +| Prompt → **Figma** | `pine-figma` (this) | Resolve every element from the published Pine library; bind every value; gate on the adoption audit. | + +## What's here + +- `SKILL.md` — the compact router (the four steps + which libraries to use/avoid). +- `reference/` — lazy-loaded detail: + - `library.md` — find & insert Pine component instances (never draw them). + - `primitives.md` — text and layout have no component; the text-style→`pds-text` + and auto-layout→`pds-box` maps, and the bind-don't-hardcode rule. + - `audit.md` — the gate: detached instances, unbound values, off-library styles. + +## Why this skill exists + +Code Connect (`libs/figma/*.figma.ts`), the Pine text/color/spacing **variables**, +and the `pine` code skill already make the **read** direction good — a design +built from instances hands off clean `pds-*` code. What was missing is a guardrail +on the **author** direction: nothing forced AI-authored Figmas to compose from +instances and bound variables in the first place. Without that, `generate_figma_design` +draws shapes, the layers panel fills with rectangles, and the result looks like the +system but has no relationship to it. This skill is that guardrail. + +## Relationship to the Figma MCP's own skills + +The Figma plugin ships `/figma-generate-design` (how to translate a layout into +Figma) and friends. Those are **framework-neutral**. `pine-figma` is the +**Pine-specific grounding overlay** on top of them: run the Figma generate/use +workflow, but resolve everything from the Pine libraries and gate on the Pine +adoption audit. Use both together — the generic *how*, plus the Pine *what*. + +## Install + +**Prerequisite — the Figma MCP** (the official Figma plugin), which provides +`search_design_system`, `get_variable_defs`, `get_design_context`, +`generate_figma_design`, etc. The skill gates on these. + +**Manual install.** Copy `skills/pine-figma/` into your agent's skills directory +(e.g. `.claude/skills/pine-figma/`). + +Once the docs-site `/.well-known/skills/` manifest generator (added alongside the +`pine` skill) is generalized to enumerate every `skills/*` directory, this skill +will also be installable via `npx skills add https://`. Until +then, install it manually. (Follow-up: generalize the generator in +`libs/core/.storybook/main.js` from the single hardcoded `skills/pine` to all +`skills/*`.) + +## License + +MIT (per `SKILL.md` frontmatter). diff --git a/skills/pine-figma/SKILL.md b/skills/pine-figma/SKILL.md new file mode 100644 index 000000000..1af019202 --- /dev/null +++ b/skills/pine-figma/SKILL.md @@ -0,0 +1,80 @@ +--- +name: pine-figma +description: Author or edit Figma UI that actually *uses* the Pine design system — compose from published Pine component instances, bind every value to a Pine variable or text style, and never hand-draw detached frames or hardcode px. Use whenever generating, editing, or reviewing a Figma design (Figma MCP `generate_figma_design` / `use_figma`, or Figma Make / First Draft output) that should conform to Pine. +license: MIT +--- + +# pine-figma — build Figma that *uses* Pine, not just *looks* like it + +The failure this skill exists to prevent: an AI-authored Figma that visually +resembles Pine but is made of **detached rectangles with hardcoded px and +off-library text**. It has no relationship to code, its layers panel shows raw +frames instead of component instances, and Dev Mode hands off CSS instead of +`pds-*`. It looks like the system and doesn't use it. + +The rule is the mirror image of the `pine` code skill: **every element on the +canvas comes from the published Pine library, and every value is a bound +variable or text style — never a raw number or hex typed from memory.** + +## When this applies + +- Generating a screen or component into Figma (`generate_figma_design`, `use_figma`). +- Editing an existing Figma file that should conform to Pine. +- Reviewing an AI- or human-made Figma for "looks like but doesn't use" drift. + +If the work is **code**, not canvas, use the `pine` skill instead. This is its +design-side companion. + +## The four steps + +**resolve → compose → bind → audit**, the same shape as the code workflow. + +1. **Orient on the real library.** `search_design_system` / `get_libraries` + against the Pine libraries below. Never invent a component or a style name — + find the published one. Your memory of Pine's Figma is not reliable; the + library search result wins. + +2. **Compose from instances.** Insert *published component instances*, not + shapes that look like them. A hand-drawn "button" rectangle is the exact + defect this skill prevents. If a piece has no matching component, it is a + **primitive** (text or layout) → step 3, not a freehand frame. + +3. **Bind every value.** Text uses a Pine **text style**; spacing, color, and + radius use Pine **variables**. No hardcoded px, no off-library hex, no + detached type. The primitive→code map lives in `reference/primitives.md`. + +4. **Audit (this is a gate, not a suggestion).** Run `get_variable_defs` + + `get_design_context` over the result and flag every detached instance, + unbound value, and off-library style. Fix, then re-audit. Only a clean audit + is done. See `reference/audit.md`. + +## The libraries — resolve from these, avoid the others + +Pull **only** from the live Pine libraries: + +- **`❖ Pine components`** — component instances (button, input, alert, select, …). +- **`✳ Pine styles`** — text styles: `typography/heading/1..6`, `typography/body`, + `typography/body-sm`, `typography/body/bold|medium|mono`, `brand-text`. +- **`✦ Pine icons`** — the icon set (`@pine-ds/icons`). +- **Pine variables** — spacing, color, radius (the Tokens Studio collection that + also builds the `--pine-*` CSS tokens, so canvas and code share one source). + +**Never** pull from these — they look almost right and are wrong: + +- `⛔⛔⛔ DO NOT USE - Sage components ⛔⛔⛔` +- `⛔⛔⛔ [DO NOT USE] Old Mercury styles ⛔⛔⛔` + +If a search result comes from a `⛔`/`DO NOT USE` library, discard it and search +again for the Pine equivalent. + +## Why grounding, not vibes + +`generate_figma_design` will happily *draw* a button. Grounding makes it *insert +the Pine button instance* — which is the only version that Code Connect can +round-trip back to ``, and the only version a designer can restyle by +switching a variant instead of editing pixels. Instances plus bound values are +what make a design a real consumer of the system instead of a screenshot of it. + +Detail lives in `reference/` — read a file when its step applies: +`library.md` (find & insert), `primitives.md` (text/layout → code map), +`audit.md` (the gate). diff --git a/skills/pine-figma/reference/audit.md b/skills/pine-figma/reference/audit.md new file mode 100644 index 000000000..7e9bf3dc8 --- /dev/null +++ b/skills/pine-figma/reference/audit.md @@ -0,0 +1,63 @@ +# The audit gate + +Authoring is not done until the design passes an audit for "uses Pine," the same +way the `pine` code skill isn't done until the layout validators pass. Run this +every time you generate or edit Pine UI in Figma, and loop until clean. + +## What to check + +Over the frame you produced (or are reviewing): + +1. **Instances, not shapes.** Every interactive/structured element is a + component instance from `❖ Pine components`. No rectangle-buttons, no + frame-inputs, no detached instances. +2. **Text is styled.** Every text layer has a `✳ Pine styles` `typography/*` + style applied. No raw font size/weight. +3. **Values are bound.** Every gap, padding, radius, fill, and border resolves to + a Pine **variable**, not a typed number or hex. +4. **No `⛔` libraries.** Nothing pulled from `DO NOT USE - Sage components` or + `Old Mercury styles`. + +## How to check with the MCP + +``` +get_variable_defs({ nodeId, fileKey }) +# → lists the variables actually bound on the node. A spacing/color/radius +# that you set but that does NOT appear here is hardcoded — that's a finding. + +get_design_context({ nodeId, fileKey }) # (or get_metadata for structure) +# → inspect the node tree: instances vs raw frames/rectangles, which library +# each instance comes from, which text layers carry a style. +``` + +A value that comes back from `get_variable_defs` as a **raw number/hex instead of +a token name** is the concrete signal of an unbound value. A child that is a +`FRAME`/`RECTANGLE` where a component was expected is a detached/hand-drawn +element. Both are findings. + +## The loop + +1. Audit the frame against the four checks. +2. For each finding, fix it at the source: swap the shape for the instance, + apply the text style, bind the value, or replace the `⛔` asset with its Pine + equivalent. +3. **Re-audit.** A fix can surface another (binding a gap reveals a detached + parent). Only a clean pass counts. + +## Non-negotiables + +- **Don't present an unaudited Figma as "Pine-conformant."** "It looks right" is + not "it uses the system" — a detached button and a real one are pixel-identical + and only one round-trips to code. +- **Don't paper over a gap.** If a needed variant/variable genuinely doesn't + exist in Pine, surface it as a **design-system gap to raise**, not a detach or + a hardcoded value to sneak in. + +## This is also the seed for an automated check + +The four checks above are mechanical: instances-not-shapes, styled-text, +bound-values, no-`⛔`-libraries — all readable from `get_variable_defs` + +`get_design_context`. They are the spec for a repo-side "Figma adoption audit" +(the design twin of the `no-hardcoded-colors` / `prefer-pds-box-layout` code +linters): point it at a file, report unbound values and detached instances, and +turn "it feels like too many px" into a number a design review can hold. diff --git a/skills/pine-figma/reference/library.md b/skills/pine-figma/reference/library.md new file mode 100644 index 000000000..9caf5c8d8 --- /dev/null +++ b/skills/pine-figma/reference/library.md @@ -0,0 +1,51 @@ +# Finding and inserting Pine from the library + +The single most important habit: **insert an instance you found by searching the +live library**, never a shape you drew to look like one. This is what fills the +layers panel with `Pine/Button` instances instead of anonymous `Rectangle` / +`Frame` nodes — the difference a reviewer sees at a glance. + +## Resolve, don't invent + +``` +search_design_system({ query: "button", fileKey: "" }) +# → returns matching components + their library. Insert the one from +# "❖ Pine components". Its componentKey is the thing to instantiate. + +search_design_system({ query: "primary secondary destructive", fileKey }) +# → use the returned variant/property names to set the instance, don't guess. +``` + +- If the best match's `libraryName` is a `⛔ DO NOT USE` Sage or Mercury library, + **discard it** and search again — those render almost identically and are the + classic source of "looks like Pine, isn't Pine." +- If nothing in `❖ Pine components` fits, the piece is probably a **primitive** + (text or layout), not a missing component. Go to `primitives.md`; do not + hand-assemble a component out of shapes + tokens. + +## The three Pine libraries and what each is for + +| Library | Holds | Use it for | +|---|---|---| +| `❖ Pine components` | Component instances | Every interactive/structured element: button, input, select, alert, chip, tabs, modal, avatar, … | +| `✳ Pine styles` | Text styles | All text — apply a `typography/*` style, never a raw font size/weight | +| `✦ Pine icons` | Icon components | Every icon (maps to `@pine-ds/icons`) | +| Pine variables (Tokens Studio collection) | Spacing / color / radius values | Every gap, padding, fill, border, corner radius | + +## The tell you're doing it right + +- **Layers panel** shows component instances (with the diamond/instance glyph), + not a stack of rectangles and text nodes. +- **Selecting an instance** shows component *properties* (Variant, Size, State) in + the right rail — the same enums the code component exposes — not raw fills and + strokes. +- **Detaching** an instance to "tweak" it is the anti-pattern. If you need a + variant that doesn't exist, that's a gap to raise, not a detach to make. + +## Why this is the load-bearing step + +Code Connect (`libs/figma/*.figma.ts`) can only round-trip a design back to +`pds-*` code when the design is built from **connected component instances**. A +detached rectangle has no node for Code Connect to attach to, so it hands off as +CSS. Composing from instances is therefore not a style preference — it's the +precondition for the whole code↔design bridge to work. diff --git a/skills/pine-figma/reference/primitives.md b/skills/pine-figma/reference/primitives.md new file mode 100644 index 000000000..452afd9a5 --- /dev/null +++ b/skills/pine-figma/reference/primitives.md @@ -0,0 +1,71 @@ +# Primitives: text and layout have no component — bind them instead + +Most of any screen is **text** and **layout (spacing)**. Neither has a Pine +*component* to instantiate: + +- **Text** in Pine's Figma is a set of **text styles** (`✳ Pine styles`), not a + "Text" component. You apply a style to a text layer. +- **Layout** is **auto-layout** with **bound spacing/radius variables**, not a + "Box" or "Stack" component. + +This is exactly why AI-drawn Figmas leak into raw px: the two things every screen +is mostly made of are the two things you can't drag in from the components panel. +The relationship is preserved a different way — **by using the right text style +and by binding every spacing value to a Pine variable**, so the primitive maps +cleanly to `pds-text` / `pds-box` in code (and Dev Mode + the `pine` code skill +translate it for you). + +## Text → `pds-text` + +Apply a `✳ Pine styles` text style. Never set a raw font size/weight/line-height. + +| Figma text style (`✳ Pine styles`) | Code | +|---|---| +| `typography/heading/1` … `/6` | `` … `tag="h6">` | +| `typography/body/@` (default) | `` (default body) | +| `typography/body/bold` | `` | +| `typography/body/medium` | `` | +| `typography/body/mono` | `` with the mono style | +| `typography/body-sm/@` | `` | +| `typography/body-sm/bold` | `` | +| `typography/body/brand-text`, `body-sm/brand-text` | brand text styles (marketing surfaces) | + +`pds-text` value ranges (from the component API — resolve via the `pine` skill / +`get_pine_component` if unsure): `size` ∈ `2xs…2xl` and `h1…h6`; `tag` is the +semantic element (`h1…h6`, `p`, `span`, …); `align` ∈ `start|center|end|justify`. +An invalid `size` is silently dropped, so it must come from the style, not memory. + +**Anti-pattern:** a text layer with a hand-typed 18px / 600 weight and no style +applied. That's the leak. Apply `typography/heading/…` (or `body/…`) instead. + +## Layout → `pds-box` (auto-layout + bound variables) + +Build layout with **auto-layout frames**, and bind spacing/radius to **Pine +variables** — never a typed px gap or corner radius. + +| Auto-layout property | Bind to | Code (`pds-box`) | +|---|---|---| +| Direction (horizontal / vertical) | — | `direction="row"` / `direction="column"` | +| Item spacing (gap) | Pine **spacing** variable | `gap=""` (e.g. `sm`, `md`, `lg`) | +| Padding | Pine **spacing** variable | box padding / surrounding spacing utility | +| Primary-axis align | — | `justify-content="start\|center\|end\|space-between\|space-around\|space-evenly"` | +| Counter-axis align | — | `align-items="start\|center\|end\|baseline\|stretch"` | +| Corner radius | Pine **radius** variable | `border-radius="none\|xs\|sm\|md\|lg\|circle"` | +| Fill / background | Pine **color** variable | `background-color=""` | +| Border color | Pine **color** variable | `border` + `border-color=""` | + +The names line up on purpose: a `gap` bound to the `md` spacing variable in Figma +is `gap="md"` in code, and both resolve to the same `--pine-dimension-*` value. +That shared source is what keeps canvas and code from drifting. + +**Anti-pattern:** an auto-layout frame with a literal `24` gap and a `12` corner +radius typed in. Bind them to the spacing/radius variables of the same value; if +the exact value has no variable, that's a token gap to raise — not a number to +hardcode. + +## Binding is the whole game + +A value that is *bound to a variable* shows up in `get_variable_defs` as a token +name. A value typed by hand shows up as a raw number — and a raw number is the +signal, in the audit (`audit.md`), that the design has slipped off the system. +Bind first; audit confirms.