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
15 changes: 15 additions & 0 deletions .claude/commands/export-design-system.md
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,21 @@ If the script exited non-zero, surface the exit code and the stderr message verb
- **Output directory exists (exit 1):** Ask the user whether to `--force` overwrite or pick a new path.
- **Unsupported framework (exit 3):** Only `react`, `vue`, `svelte`, `react-native` are supported.

## Round-trip / re-import

The export is reversible. To reconstruct a `design-tokens.lock.json` from an
exported workspace — or to verify the round-trip in CI — use the inverse script:

```bash
./scripts/import-design-tokens.sh --from dist/design-system --out design-tokens.lock.json
./scripts/import-design-tokens.sh --from dist/design-system --verify src/styles/design-tokens.lock.json
```

Only `tokens.json`/`tokens.ts` round-trip losslessly (`tokens.css` and the
Tailwind preset are derived views). See `docs/design-system-export/consumers.md`
for the interchange contract and how Flavian (WordPress `theme.json`) and Nerva
(typed tokens) consume the export.

## Out of scope

This command does NOT:
Expand Down
24 changes: 24 additions & 0 deletions .claude/skills/export-design-system/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -167,9 +167,33 @@ Common flag combinations:
- **The script does not run `pnpm install` or `pnpm build`.** It only writes files. The user runs install/build to verify the workspace.
- **Component file filtering is conservative.** `*.test.*`, `*.spec.*`, `*.stories.*`, `*.d.ts` are excluded. Co-located CSS modules and helper files with matching framework extensions ARE included.

## Re-importing & Round-Trip

The exported `tokens.json` is a **lossless** snapshot of the lockfile, so the
export is fully reversible. `scripts/import-design-tokens.js` (the inverse of this
skill) reconstructs a `design-tokens.lock.json` from an exported package:

```bash
# Reconstruct a lockfile from an exported workspace
./scripts/import-design-tokens.sh --from dist/design-system --out design-tokens.lock.json

# Verify the round-trip (exit 0 if identical to the source, 1 if it drifted)
./scripts/import-design-tokens.sh --from dist/design-system --verify src/styles/design-tokens.lock.json
```

`--from` also accepts an installed package or a raw `tokens.json` path. Only
`tokens.json`/`tokens.ts` round-trip; `tokens.css` and `tailwind-preset.ts` are
derived, one-way views. See **`docs/design-system-export/consumers.md`** for the
interchange contract and how the named consumers ingest the export — **Flavian**
(WordPress/FSE `theme.json`) and **Nerva** (typed tokens in a Hono/Workers
backend). The guarantee is proved by `scripts/__tests__/design-system-roundtrip.test.js`.

## Reference

- Script: `scripts/export-design-system.js`
- Wrapper: `scripts/export-design-system.sh`
- Slash command: `/export-design-system`
- Re-importer: `scripts/import-design-tokens.js` / `scripts/import-design-tokens.sh`
- Round-trip test: `scripts/__tests__/design-system-roundtrip.test.js`
- Consumer contract: `docs/design-system-export/consumers.md`
- Inputs: `design-tokens.lock.json` (from `design-token-lock` skill), `build-spec.json` (from `figma-intake`/`canva-intake`/`screenshot-intake` skills)
4 changes: 4 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,9 @@ node scripts/validate-pipeline-config.js --config <path> --schema <path>
# Export generated components as a publishable design-system workspace
./scripts/export-design-system.sh [--scope @org] [--output dir] [--framework <react|vue|svelte|react-native>] [--dry-run] [--json]

# Re-import: reconstruct design-tokens.lock.json from an export (inverse of export; round-trip / consumer)
./scripts/import-design-tokens.sh [--from dir|tokens.json] [--out path|-] [--verify lockfile] [--force] [--json]

# Renderer registry: list / resolve / detect framework renderers (see docs/multi-framework/renderers.md)
node scripts/renderer-registry.js (list | resolve <name> | detect [dir]) [--json]
node scripts/validate-renderer.js --dir <renderer-dir> # or --all [--renderers-root <dir>]
Expand Down Expand Up @@ -584,6 +587,7 @@ gh issue create # Create issue
./scripts/capture-baselines.sh # Capture regression baselines
./scripts/regression-test.sh # Visual regression testing
./scripts/export-design-system.sh # Export components + tokens as pnpm workspace
./scripts/import-design-tokens.sh # Reconstruct lockfile from an export (round-trip / consumer)
./scripts/validate-pipeline-config.sh # Validate pipeline.config.json against schema
./scripts/check-doc-counts.sh # Flag drift between documented agent/skill counts and disk
./scripts/verify-all.sh # Run all quality checks with summary
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -345,6 +345,7 @@ Details: `.claude/PLUGINS-REFERENCE.md`
| Canva pipeline guide | `docs/canva-to-react/README.md` | Canva pipeline overview and troubleshooting |
| Conversation pipeline guide | `docs/conversation-to-app/README.md` | Conversational app creation via generated Figma designs |
| InDesign pipeline guide | `docs/pipelines/indesign.md` | Convert exported IDML/PDF into React components, tokens, and Storybook stories |
| Design system export | `docs/design-system-export/consumers.md` | Export format, round-trip re-import, and how Flavian/Nerva consume the tokens |
| Agent catalog | `.claude/CUSTOM-AGENTS-GUIDE.md` | All 56 agents with use cases |
| Skills catalog | `.claude/skills/README.md` | All 24 skills with triggers |
| Plugin reference | `.claude/PLUGINS-REFERENCE.md` | Plugin configuration and commands |
Expand Down
172 changes: 172 additions & 0 deletions docs/design-system-export/consumers.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
# Design System Export — Consumers & Round-Trip Contract

`/export-design-system` turns a project's `design-tokens.lock.json` + components
into a publishable pnpm workspace (see the `export-design-system` skill for the
full output layout). This document defines **what a downstream project imports**
and the **round-trip guarantee** that makes the export a reliable interchange.

## The interchange contract

The exported `@scope/design-tokens` package ships four token artifacts. Two are
lossless reconstructions of the source lockfile; two are derived, ergonomic
_views_:

| Artifact | Contents | Lossless? | Use it for |
|----------|----------|-----------|------------|
| `tokens.json` | verbatim JSON snapshot of `design-tokens.lock.json` | **Yes** | machine interchange, re-import, custom tooling |
| `tokens.ts` | `export const tokens = <lockfile> as const` | **Yes** | typed, tree-shakeable access in TS/JS consumers |
| `tokens.css` | flattened `--color-* / --space-* / --radius-*` custom properties | No | drop-in theming for web UIs |
| `tailwind-preset.ts` | `{ colors, spacing, borderRadius, fontFamily }` subset | No | extend a consumer's Tailwind config |

**`tokens.json` is the canonical interchange.** It is a byte-for-byte snapshot of
the lockfile, so any consumer that reads it sees exactly the tokens the design
system was locked to. `tokens.css` and `tailwind-preset.ts` are one-way
projections — they intentionally drop structure (nested color scales, typography
families, `textContent`, `rgb`/`tailwind` metadata) and therefore **cannot** be
reversed back into a lockfile.

## Round-trip guarantee

`import-design-tokens` is the inverse of the exporter and the reference consumer.
It reads the lossless `tokens.json` and reconstructs an identical
`design-tokens.lock.json`:

```
lock ──/export-design-system──▶ @scope/design-tokens ──import-design-tokens──▶ lock′
lock′ ≡ lock
```

```bash
# Reconstruct a lockfile from an exported workspace
./scripts/import-design-tokens.sh --from dist/design-system --out design-tokens.lock.json

# From a published/installed package, or a raw tokens.json path
./scripts/import-design-tokens.sh --from node_modules/@acme/design-tokens/dist/tokens.json --out -

# Verify a round-trip in CI (exit 0 when identical, 1 when it drifts)
./scripts/import-design-tokens.sh --from dist/design-system --verify src/styles/design-tokens.lock.json
```

The `--verify` mode is the executable form of the guarantee: it fails loudly if an
export ever stops being a faithful representation of its source tokens. The
automated proof lives in `scripts/__tests__/design-system-roundtrip.test.js`
(tokens → export → reimport → deep-equal).

`--from` accepts an export root, a `design-tokens` package directory, or a
`tokens.json` file directly. It probes, in order:
`packages/design-tokens/src/tokens.json` → `.../dist/tokens.json` →
`src/tokens.json` → `dist/tokens.json` → `tokens.json`.

## Consumers

### Aurelius (self-consumer)

Aurelius re-imports its own export to move a locked design system between
projects, or to validate that a hand-edited token package still round-trips. This
is the `import-design-tokens` path above.

### Flavian — WordPress / FSE `theme.json`

[Flavian](https://github.com/PMDevSolutions/Flavian) is a Claude Code-integrated
WordPress framework. A block theme's design surface is `theme.json`, so the
consumer installs the tokens package and maps `tokens.json` into
`theme.json.settings`:

| Lockfile section | `theme.json` target |
|------------------|---------------------|
| `colors.semantic.<k>.hex` (fallback `colors.primitives`) | `settings.color.palette[]` — `{ slug, color, name }` |
| `typography.families.<k>` (`value` + `fallback`) | `settings.typography.fontFamilies[]` — `{ slug, fontFamily, name }` |
| `spacing.scale.<k>.px` | `settings.spacing.spacingSizes[]` — `{ slug, size: "<px>px", name }` |
| `borderRadius.<k>.px` | `settings.custom.radius.<k>` → emits `--wp--custom--radius--<k>` |

Example adapter (run in the theme's build step):

```js
// scripts/tokens-to-theme-json.mjs
import { readFileSync, writeFileSync } from "node:fs";
// From the installed package: @acme/design-tokens/tokens.json
import tokens from "@acme/design-tokens/tokens.json" with { type: "json" };

const palette = Object.entries(tokens.colors?.semantic ?? {}).map(([slug, v]) => ({
slug,
color: v.hex,
name: slug.replace(/(^|-)\w/g, (m) => m.toUpperCase().replace("-", " ")),
}));

const fontFamilies = Object.entries(tokens.typography?.families ?? {}).map(([slug, v]) => ({
slug,
fontFamily: [v.value, v.fallback].filter(Boolean).join(", "),
name: slug,
}));

const spacingSizes = Object.entries(tokens.spacing?.scale ?? {}).map(([slug, v]) => ({
slug,
size: `${v.px}px`,
name: slug,
}));

const radius = Object.fromEntries(
Object.entries(tokens.borderRadius ?? {}).map(([k, v]) => [k, `${v.px}px`]),
);

const themeJson = {
version: 3,
settings: {
color: { palette },
typography: { fontFamilies },
spacing: { spacingSizes },
custom: { radius },
},
};

writeFileSync("theme.json", JSON.stringify(themeJson, null, 2) + "\n");
```

Bumping the tokens package version (via Changesets) and re-running the adapter is
all that is needed to propagate a token change into the theme.

### Nerva — Hono / Cloudflare Workers backend

[Nerva](https://github.com/PMDevSolutions/Nerva) is a Claude Code-integrated API
framework (Hono, Cloudflare Workers, Drizzle ORM). Backends need tokens for
server-rendered surfaces — transactional emails, PDFs, or a design-tokens API —
where CSS is not available. The consumer imports the **typed** tokens, which are
tree-shakeable and require no runtime CSS:

```ts
import { Hono } from "hono";
import { tokens, type Tokens } from "@acme/design-tokens"; // typed, from tokens.ts

const app = new Hono();

// Serve the canonical tokens to any client (mobile app, docs site, design tool)
app.get("/design-tokens", (c) => c.json(tokens));

// Or use tokens directly when rendering an email template server-side
function emailButton(label: string) {
const primary = tokens.colors.semantic.primary.hex;
const radius = tokens.borderRadius.md.px;
return `<a style="background:${primary};border-radius:${radius}px;color:#fff">${label}</a>`;
}

export default app;
```

Because `@acme/design-tokens` is framework-agnostic and side-effect free (apart
from the optional `tokens.css`), it bundles cleanly into a Workers runtime.

## Style Dictionary

`tokens.json` is a plain nested map, so a consumer that standardizes on
[Style Dictionary](https://styledictionary.com/) can register it as a custom
source and run its own transforms. Aurelius does **not** require a Style
Dictionary build step — it ships ready-to-use CSS variables, a typed TS export,
and a Tailwind preset directly — but nothing prevents layering Style Dictionary on
top of the lossless `tokens.json`.

## Reference

- Exporter: `scripts/export-design-system.js` (skill: `export-design-system`, command: `/export-design-system`)
- Re-importer: `scripts/import-design-tokens.js` / `scripts/import-design-tokens.sh`
- Round-trip test: `scripts/__tests__/design-system-roundtrip.test.js`
- Lockfile source of truth: `design-token-lock` skill → `design-tokens.lock.json`
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
# Design System Export — Consumer Contract & Round-Trip

**Issue:** #82 — Design system export command (`/export-design-system`)
**Date:** 2026-07-01
**Milestone:** v2.0.0

## Problem

`/export-design-system`, `scripts/export-design-system.{js,sh}`, and the
`export-design-system` skill already ship on `main` (acceptance criteria #1 and
#2). Two gaps remain from the v2.0.0 scope:

- **#3 — a defined consumer.** The export is produced but nothing documents how a
downstream project ingests it. Sibling frameworks
[Flavian](https://github.com/PMDevSolutions/Flavian) (WordPress/FSE) and
[Nerva](https://github.com/PMDevSolutions/Nerva) (Hono + Cloudflare Workers) are
the named consumers.
- **#4 — a round-trip guarantee.** Nothing proves the export is a faithful,
reversible representation of the source tokens.

## Key insight: which artifact round-trips

The exported `@scope/design-tokens` package ships four token artifacts:

| Artifact | Contents | Lossless? |
|----------|----------|-----------|
| `tokens.json` | `JSON.stringify(lockfile)` — verbatim snapshot | **Yes** |
| `tokens.ts` | `export const tokens = <lockfile> as const` | **Yes** |
| `tokens.css` | flattened `--color/--space/--radius` custom properties | No (drops `typography`, `textContent`, `rgb`/`tailwind` metadata, nesting) |
| `tailwind-preset.ts` | `{colors, spacing, borderRadius, fontFamily}` subset | No |

The lockfile is a rich nested object (`colors.primitives`/`colors.semantic`,
`typography.families`, `spacing.scale`, `borderRadius`, `textContent`, plus
`version`/`figmaFileKey`/`figmaLastModified` metadata). Only `tokens.json` and
`tokens.ts` preserve it fully. **`tokens.json` is therefore the canonical lossless
interchange**; `tokens.css`/`tailwind-preset.ts` are derived, consumer-facing
*views*, never the source of truth for a re-import.

## Solution

### 1. Reimporter — `scripts/import-design-tokens.js` (+ `.sh` wrapper)

The inverse of the exporter and the first concrete consumer. Reads an exported
tokens package and reconstructs a `design-tokens.lock.json`.

**CLI**

```
node scripts/import-design-tokens.js [options]
--from <path> export root, tokens package dir, or a tokens.json file
(default: dist/design-system — matches the exporter default)
--out <path> where to write the reconstructed lockfile
(default: design-tokens.lock.json; use "-" for stdout)
--verify <path> compare the reconstructed lockfile against a reference
lockfile; exit 0 if identical, 1 if not (no file written)
--force overwrite --out if it exists
--json machine-readable summary
-h, --help
```

**`--from` resolution order** (first match wins): a `*.json` file used directly;
otherwise a directory is probed at `packages/design-tokens/src/tokens.json` →
`packages/design-tokens/dist/tokens.json` → `src/tokens.json` →
`dist/tokens.json` → `tokens.json`.

**Behavior:** parse `tokens.json`; reject input that is not a token lockfile
(must contain at least one of `colors`/`typography`/`spacing`/`borderRadius`);
then either `--verify` (deep-equal vs reference) or write the lockfile
(refusing to clobber `--out` without `--force`).

**Exit codes** mirror the exporter: `0` ok, `1` output exists / verify mismatch,
`2` no tokens.json found under `--from`, `3` input is not a token lockfile.

### 2. Round-trip test — `scripts/__tests__/design-system-roundtrip.test.js`

Vitest, driving the real scripts via `execFileSync`:

1. Write a rich fixture `design-tokens.lock.json` (every top-level section).
2. Export: `export-design-system.js --lockfile <fixture> --framework react --output <tmp> --force`.
3. Assert `<tmp>/packages/design-tokens/src/tokens.json` deep-equals the fixture.
4. Reimport: `import-design-tokens.js --from <tmp> --out <tmp>/reimported.lock.json`.
5. **Deep-equal the reimported lockfile against the original** — the core guarantee.
6. `--verify` returns exit 0 for the matching lockfile, non-zero for a mutated one.
7. Import directly from a `tokens.json` path also round-trips.

### 3. Consumer documentation — `docs/design-system-export/consumers.md`

- The interchange contract: `tokens.json` is lossless and stable; `tokens.css` /
`tailwind-preset.ts` / typed `tokens.ts` are the ergonomic views.
- **Aurelius** (self-consumer): the reimporter + the round-trip guarantee.
- **Flavian** (WordPress/FSE): `pnpm add @scope/design-tokens`, then map
`tokens.json` → `theme.json` (`settings.color.palette`,
`settings.typography.fontFamilies`, `settings.spacing.spacingSizes`) with a
documented mapping table and an example adapter snippet.
- **Nerva** (Hono/Workers): `import { tokens } from '@scope/design-tokens'` for
typed, tree-shakeable tokens in backend/email/PDF theming; example.
- Style Dictionary note: `tokens.json` is a plain nested map and can be fed to
Style Dictionary as a custom source if a consumer prefers that toolchain — we
do not require it.

### 4. Wiring

- `export-design-system` skill + `/export-design-system` command: add a
"Re-importing / round-trip" section pointing at the reimporter and the doc.
- `CLAUDE.md` + `README.md`: document the new script; bump the manual script count.

## Out of scope (YAGNI)

- Generating consumer-specific adapter files (e.g. emitting `theme.json`) — the
export→adapter mapping is *documented* with example snippets, not generated.
- Reconstructing a lockfile from lossy `tokens.css` — impossible to do faithfully;
`tokens.json` is the interchange.

## Files

| File | Action |
|------|--------|
| `scripts/import-design-tokens.js` | Create — reimporter |
| `scripts/import-design-tokens.sh` | Create — thin wrapper |
| `scripts/__tests__/design-system-roundtrip.test.js` | Create — round-trip test |
| `docs/design-system-export/consumers.md` | Create — consumer contract + Flavian/Nerva |
| `.claude/skills/export-design-system/SKILL.md` | Update — round-trip/reimport section |
| `.claude/commands/export-design-system.md` | Update — reference reimport + round-trip |
| `CLAUDE.md`, `README.md` | Update — script docs + counts |
Loading
Loading