Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
60 changes: 60 additions & 0 deletions skills/pine-figma/README.md
Original file line number Diff line number Diff line change
@@ -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://<pine-docs-domain>`. 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).
80 changes: 80 additions & 0 deletions skills/pine-figma/SKILL.md
Original file line number Diff line number Diff line change
@@ -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 `<pds-button>`, 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).
63 changes: 63 additions & 0 deletions skills/pine-figma/reference/audit.md
Original file line number Diff line number Diff line change
@@ -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.
51 changes: 51 additions & 0 deletions skills/pine-figma/reference/library.md
Original file line number Diff line number Diff line change
@@ -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: "<the file you're authoring>" })
# → 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.
71 changes: 71 additions & 0 deletions skills/pine-figma/reference/primitives.md
Original file line number Diff line number Diff line change
@@ -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` | `<pds-text tag="h1">` … `tag="h6">` |
| `typography/body/@` (default) | `<pds-text>` (default body) |
| `typography/body/bold` | `<pds-text weight="bold">` |
| `typography/body/medium` | `<pds-text weight="medium">` |
| `typography/body/mono` | `<pds-text>` with the mono style |
| `typography/body-sm/@` | `<pds-text size="sm">` |
| `typography/body-sm/bold` | `<pds-text size="sm" weight="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="<token>"` (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="<bound color>"` |
| Border color | Pine **color** variable | `border` + `border-color="<bound 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.
Loading