Design System Contracts

How it works · foundations 6

Round-trips

The piece that settles the source-of-truth argument: every change on either surface becomes a reviewable proposal to the contract, and the contract regenerates the other side. Executed end-to-end, in both directions, with receipts committed.

The promotion loop: a hand edit on one surface is flagged by the differ with a complete proposed contract patch, promoted by a human into the contract with a version bump, regenerated onto both surfaces, and verified — a surface that lags is named until it catches up.The promotion loop: a hand edit on one surface is flagged by the differ with a complete proposed contract patch, promoted by a human into the contract with a version bump, regenerated onto both surfaces, and verified — a surface that lags is named until it catches up.

Direction 1 — code ahead (the product engineer)

  1. Hand-edit the generated code

    loading?: boolean added by hand to Button.tsx — the thing engineers actually do.

  2. The differ flags it

    npm run parity[code AHEAD] Button.loading, with a complete proposed prop patch, design-side binding included.

  3. Promotion

    Patch applied to contracts/button.contract.json; version bumped 1.0.0 → 1.1.0.

  4. Regenerate

    npm run build — the hand edit superseded by the contract-governed version; the Loading property pushed to the live canvas set.

  5. Clean

    Snapshot refreshed → npm run parity → clean.

This exact loop is replayed with the real differ and real emitters, step by step, on How are properties added?

Direction 2 — canvas ahead (the designer)

A designer's edit on the canvas — a dark-mode surface alias retargeted — comes back as a MISMATCH finding with a proposed token patch. Promotion updates tokens/modes/semantic.dark.tokens.json; the token build re-emits the CSS custom property; the code workshop's dark mode reflects it immediately; parity returns clean. Same door in both directions: a diffable change to a JSON file in Git, reviewable by designers and engineers alike. (Full narrative with the operational learnings: docs/06 — The Parity Loop.)

Round-trip identity — the standing check

Beyond the executed demo, the repo's own generated components are continuously re-extracted — code→contract and design→contract — and matched against their shipping contracts: zero mismatches, both directions, red-tested (the checks are proven able to fail). Receipts: code round-trip · plugin-dump round-trip · REST round-trip.

What the loop caught on its very first run

The first baseline npm run parity flagged that the canvas Button's default Size was Small while the contract says md. Root cause: the design tool's default variant is positional (top-left of the set), and the generator had laid variants out in enum order. Fixed twice over — the live set re-arranged, and the generator now orders every axis default-value-first. Not staged; the loop caught a real gap on day one.

What "parity clean" does and doesn't mean

The differ verifies the contracted API surface: props with types and kinds, variant axes and option sets, defaults on both surfaces, slot properties and their accepts, nested instances, and every token variable. It does not continuously inspect anatomy internals below the API surface — those are enforced at generation time and re-verified visually by the visual-parity instrument, and the docs say so rather than rounding up.