Author the design language once in the canonical React designbook and project it
one-way onto each stack: React -> designbook/ -> ir/ -> adapters/<stack>/.
Phase 0 — contracts & governance (T01-T03):
- ir/SCHEMA.md + ir/schema/{component,tokens}.schema.json — neutral IR contract
(W3C DTCG tokens; React prop -> HTML attribute mapping; non-portable props flagged).
- adapters/ADAPTER_CONTRACT.md — inputs, drift-report + parity-result shapes,
idempotency rules, CI exit codes (0 ok / 2 usage / 3 drift / 4 parity / 5 internal).
- .claude/rules/designbook-propagation.md + DesignSystemIntroduction.md §5.1 —
one-way directionality + drift-resolution workflow.
T04 — canonical React designbook + the missing pull tool:
- The bundled /design-sync skill only PUSHES repo->cloud; it cannot populate
designbook/. Added scripts/designbook_pull.py + `make designbook-pull`, which drives
the local claude binary headless (acceptEdits) so DesignSync fetch+write runs in a
subprocess (contents never hit the orchestrator's context). Pulled 44 files;
excludes the _whynot-design-seed/ self-copy. Corrected the docs that wrongly called
/design-sync the pull.
T05 — IR extractor (scripts/ir-extract.mjs + `make ir`):
- ir/tokens.json (80 tokens, DTCG, var() -> {ref} alias resolution); ir/components/*.json
(10 contracts parsed from .jsx signatures: enum/boolean/number inference, prop->attr
map, style/callback marked non-portable); ir/exemplars/*.
T06 — Lit token adapter (adapters/lit/ + `make adapt-lit`):
- Full-gen tokens into src/styles/colors_and_type.css :root (marker-bounded, idempotent
no-op on re-run; hand-authored type CSS preserved).
NOTE: token regen synced Lit to canonical React — fonts IBM Plex -> system stacks and 8
status tokens added. This is a VISUAL change: review and run `pnpm test:visual:update`
before merge. Remaining: T07 scaffold+drift, T08 parity, T09 runbook, T10 2nd-adapter.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
4.6 KiB
Stack
- Language: ES modules (no build step, no transpile — source ships as-is).
- Key deps:
lit^3 (web components, the one runtime dep);@playwright/test(the one dev dep, visual regression). - Components: shadow-DOM Lit elements that adopt a shared stylesheet so the Layer 1
.wn-*utility classes work inside the shadow root.src/elements/_styles.jsis auto-generated fromsrc/styles/components.cssbyscripts/sync-shared-styles.mjs— never hand-edit it.
Dev Commands
pnpm install
pnpm showcase # serve :4321 → /examples/showcase/ (every component)
pnpm example # serve :4322 → examples/whynot-control
pnpm test:visual # Playwright visual-regression run (auto-starts a static server)
pnpm test:visual:update # regenerate screenshot baselines after an intentional change
pnpm check # check-changelog: fails if CHANGELOG.md gained no entry vs main
npx playwright test -g "inbox" # run a single visual test by name
make # list make targets (help is the default goal)
make designbook-sync # after a /design-sync pull, record changes + last-sync time → RecentChanges.md
make designbook-check # ask Claude Design (via llm-connect) if the cloud is newer; warn if mirror is stale
make recent-changes # regenerate RecentChanges.md (alias; ARGS="--range main..HEAD" supported)
make sync-styles # = node scripts/sync-shared-styles.mjs
There is no unit-test suite — correctness is verified by full-page Playwright screenshot diffs of the two examples/ pages (tests/visual/ui-kit.spec.mjs, maxDiffPixelRatio: 0.005). Any visual change needs pnpm test:visual:update + baseline review.
Integrating the designbook
The designbook is the upstream atelier — the Claude Design project (cloud, claude.ai/design), source of truth for the language. Its local mirror lives in-repo at designbook/ (see designbook/README.md). Sync is agent-driven and incremental (one component at a time, never a wholesale replace):
- Pull — run
make designbook-pull(scripts/designbook_pull.py). It drives the localclaudebinary headless (claude --print --permission-mode acceptEdits), which has theDesignSynctool over the claude.ai login, to fetch the React designbook and write it intodesignbook/; the file contents stay in that subprocess, so the pull is cheap regardless of size. Selection is governed bydesignbook/.design-pull.json(it excludes_whynot-design-seed/**, the cloud project's copy of this repo). The script stamps freshness itself on success. Note: the bundled/design-syncskill goes the other way — it pushes a repo up to Claude Design — so it does not populatedesignbook/; usemake designbook-pullfor the pull (seedesignbook-propagation.md). - Record —
make designbook-syncrunsscripts/designbook-sync.mjs, writingRecentChanges.md: a snapshot (not a log) of what changed acrossdesignbook/+ the derived surfaces (tokens/,src/styles/,src/elements/,examples/), grouped by layer.
Freshness is tracked in designbook/.design-sync.json (lastSyncAt, remoteUpdatedAt, projectId, projectName). Every report states "Last /design-sync: " so it's clear whether the snapshot reflects the latest design. The cloud-ahead check is backed by llm-connect (make designbook-check → scripts/check_designbook_staleness.py): it uses the claude-code adapter to ask the local claude binary for the project's current updatedAt via DesignSync.list_projects, then records it with node scripts/designbook-sync.mjs --remote-updated <iso>. Only the claude-code adapter can reach the user's Claude Design project (Gemini/OpenRouter/OpenAI cannot), and no secret goes in the prompt — DesignSync uses the claude.ai login (see credential-routing.md). The check needs llm_connect importable; the Makefile auto-selects ~/llm-connect/.venv/bin/python. Use --remote-updated <iso> to run the comparison offline/manually, or --fail-if-stale (exit 3) in automation. When remoteUpdatedAt is newer than lastSyncAt, the report and stdout warn that the local mirror is OUTDATED until the next /design-sync. If no sync was ever recorded, it warns that /design-sync has not run. The reporter is deterministic (built from git status/git diff), only writes the working tree, never commits, and never edits designbook/ content. Fold notable entries into CHANGELOG.md under ## [Unreleased] before releasing — RecentChanges.md is overwritten every run and is not the CI-enforced artifact.