# @design-parity/adapter-claude-design

The Claude Design [`ReferenceAdapter`](../../core/src/types.ts) for
design-parity. Depends only on `@design-parity/core`.

## There is no Claude Design read API

Claude Design (beta since 2026-06) exposes **no read API and no Figma export**,
so — unlike the Figma adapter (REST + Code Connect) or even Stitch (SDK) — there
is nothing to fetch at run time. The reference is consumed as a **committed HTML
export** checked into the consumer repo. Because there is no machine link to
call, the correspondence is always a `design-map.json` entry and the resulting
`DesignReference` always has `linkMethod: "manifest"`.

> **`/design-sync` (2026-06):** Claude Code's `/design-sync` now syncs a design
> system between the canvas and the repo — including pushing built UI back into
> Claude Design (the reverse direction the
> [`compose-preview-design-board`](https://github.com/yschimke/skills) skill
> used to own) and emitting committed, machine-readable tokens/components. This
> is a governed *read → plan → write* skill, **not** a read API the adapter can
> call, so the committed-export shape below is unchanged. But it does make the
> input *machine-generated-and-committed* rather than hand-authored, and opens a
> richer reference (synced tokens, see below) and a `ref → code` reverse index
> (already shipped: `buildReverseIndex` in `@design-parity/resolver`). See
> [docs/claude-design-sync-impact.md](../../../docs/claude-design-sync-impact.md).

### Push-back is `/design-sync`, not a CanvasWriter

design-parity ships **no `claude-design` `CanvasWriter`** and runs no
Code-to-Canvas push-back for this source. There is no Claude Design write API,
and `/design-sync`'s canvas push-back is an **interactive, human-run terminal
skill** — it has no place on design-parity's unattended GitHub Action path
(PRINCIPLES.md 1 "no AI in the CI loop", 4 "unattended in steady state", 5).
So the reverse direction — getting what you built into Claude Design — is
`/design-sync` itself, run by a person, and **supersedes** the older
[`compose-preview-design-board`](https://github.com/yschimke/skills) skill that
hand-built HTML to import. The Action's read-only job stays the same: resolve a
committed reference and diff it. (Contrast Figma, where `FigmaCanvasWriter` +
the opt-in `code-led` push-back exist precisely because that bridge is a
non-interactive REST/plugin write.)

```
design-map.json ──▶ design/reference/*.html ──▶ rasterize ─┐
   (manifest)         (committed export)        (headless)  ├─▶ DesignReference
                          └─ handoff manifest ──▶ tokens ───┘     (manifest)
```

## The HTML export

A committed export is an ordinary HTML document carrying one embedded handoff
manifest:

```html
<script type="application/design-parity+json">
  {
    "componentId": "ui/Card.kt#OfferCard",
    "tokens": { "spacing": { "padding": 16 }, "radius": { "corner": 12 } },
    "images": [
      { "state": "default", "theme": "light", "size": "medium",
        "src": "./offer-card.light.png" }
    ]
  }
</script>
```

- **`images[].src`** — a pre-rendered PNG, resolved relative to the HTML file.
  Its `width`/`height` are read from the PNG itself, so reference dimensions can
  never drift from the committed bytes. A variant with no `src` (or an export
  with no `images` at all) is **rasterized headlessly** from the document.
- **`tokens`** — inline `DesignTokens`, or a string path to a handoff token
  file (relative to the HTML) for token-compliance checks.
- **`componentId`** — optional; when present it must match the component the
  resolver asked for, else `resolve` throws.

## The synced token artifact (`.json` ref)

When a `design-map.json` ref ends in **`.json`**, the adapter treats it as a
**synced design-system token artifact** — a committed [W3C DTCG](https://tr.designtokens.org/)
document, typically emitted by Claude Code's `/design-sync` — instead of an HTML
export. It is loaded through `@design-parity/core`'s `loadDtcgTokens` into a
**token-only** `DesignReference`:

- `referenceImages` is `[]` and **nothing rasterizes** (no HTML, no layout
  capture) — the reference feeds the token-compliance diff only.
- `linkMethod` is still `"manifest"` (there is no read API; this is a committed
  file).
- `resolve` throws a `claude-design`-prefixed error if the file is missing, isn't
  JSON, or fails DTCG schema validation.

```jsonc
// design-map.json — point the ref at the synced DTCG document
{ "code": "ui/Card.kt#OfferCard", "source": "claude-design",
  "ref": "design/design-system.tokens.json" }
```

This is the richer of the two shapes: the token table comes straight from the
synced design system rather than an export's embedded handoff block. See
[docs/claude-design-sync-impact.md](../../../docs/claude-design-sync-impact.md).

## Live-render a prototype (`live:` ref, #85)

A committed export is a single flattened frame. When a `design-map.json` ref is
prefixed **`live:`**, the adapter instead **drives the actual clickable
prototype in a browser** and captures it at each configured **viewport** — a
truer reference that also picks up whatever the static export flattened, and one
that pairs per-cell against the candidate's device × breakpoint render matrix.

```jsonc
// design-map.json — opt one component into live-render
{ "code": "ui/Card.kt#OfferCard", "source": "claude-design",
  "ref": "live:design/prototypes/offer-card.html" }
```

- **Opt-in.** Only a `live:`-prefixed ref takes this path; every unprefixed ref
  stays on the lighter **static-export** path, unchanged. Mirrors the `figma:` /
  `stitch:` ref schemes.
- **Multi-viewport.** Each configured `LiveViewport` becomes one `Image` keyed
  by its `size` slot (`compact` / `medium` / `expanded`). The default is a single
  compact frame; pass `liveViewports` for a wider matrix.
- **Same contract.** The result is a normal `DesignReference` with
  `linkMethod: "manifest"` — the diff engine can't tell it from a static export.
- **Injectable renderer.** The default `browserLiveRenderer` drives headless
  Chrome/Chromium on `PATH` (set `CHROME_BIN`), identical in spirit to the
  rasterizer — no browser-automation dependency is bundled. A caller already
  running **Playwright** (or a hosted renderer) injects its own `liveRenderer`:

```ts
new ClaudeDesignAdapter({
  liveRenderer: myPlaywrightRenderer,
  liveViewports: [
    { size: "compact", width: 412 },
    { size: "expanded", width: 1280 },
  ],
});
```

`resolve` throws a prefixed error when the `live:` ref names no path, the
prototype is unreadable, or a configured viewport fails to render.

## Usage

```ts
import { ClaudeDesignAdapter } from "@design-parity/adapter-claude-design";

const adapter = new ClaudeDesignAdapter();
const ref = await adapter.resolve(
  "ui/Card.kt#OfferCard",          // resolver-supplied code handle
  "design/reference/offer-card.html", // the design-map ref (repo-relative)
  { repoRoot: process.cwd(), env: process.env },
);
```

### Rasterization

Rasterizing raw HTML variants defaults to `browserRasterizer`, which drives a
headless **Chrome/Chromium already on `PATH`** (set `CHROME_BIN` to point at a
specific binary) — no browser-automation dependency is bundled, keeping the
package's only runtime dependency `@design-parity/core`. Inject your own to
render inside an existing harness:

```ts
new ClaudeDesignAdapter({ rasterizer: myRasterizer });
```

Exports that ship pre-rendered `src` images never invoke a rasterizer.

## Errors

`resolve` throws a clear, prefixed error when the export is missing, its handoff
block is malformed, a referenced token file or image is missing, or the export's
`componentId` contradicts the resolver.
