Design System Contracts

How it works · question 1

How are properties added?

Through one door, from either side: a difference on a surface becomes a reviewable patch to the contract, a human promotes it, and the contract regenerates both surfaces. Everything below was produced by the real engine at site-build time.

Lifecycle of an added property: a hand edit on one surface is flagged by the differ as code AHEAD with a complete proposed contract patch; a human promotes the patch into the contract, bumping its version; both surfaces regenerate from the contract; a surface that skipped regeneration is named figma BEHIND until it catches up.Lifecycle of an added property: a hand edit on one surface is flagged by the differ as code AHEAD with a complete proposed contract patch; a human promotes the patch into the contract, bumping its version; both surfaces regenerate from the contract; a surface that skipped regeneration is named figma BEHIND until it catches up.

1 · The hand edit

An engineer needs a prop the system doesn’t have, and does what engineers do — adds it to the generated component by hand. This replay applies exactly that mutation to the repository’s real generated Button.tsx in a scratch copy:

src/components/Button/Button.tsx · 61 → 64 lines · +3 −0 — the hand edit, applied at site-build time to a scratch copy of the real generated source — the same mutation the eval detect-code-added-prop locks
    /** Shows a spinning busy indicator beside the label while an async action resolves. */
    loading?: boolean;
+   /** Renders the icon without a visible label. */+   iconOnly?: boolean;  }
  
      disabled = false,
      loading = false,
+     iconOnly = false,      className,
      children,

2 · The differ flags it — with a complete patch

The three-way differ (npm run parity) compares code, canvas, and contract. Run at site-build time over that scratch, it reports exactly one finding — below is its verbatim output, including the complete proposed contract patch, design-side binding included:

parity/diff.ts — the repository’s actual differ, run at site-build time over the scratch copy; verbatim finding
{
  "surface": "code",
  "classification": "ahead",
  "subject": "Button.iconOnly",
  "detail": "Code declares prop \"iconOnly\" (boolean) that the contract does not define",
  "proposedPatch": {
    "name": "iconOnly",
    "type": "boolean",
    "default": false,
    "bindings": {
      "figma": {
        "kind": "BOOLEAN",
        "property": "IconOnly"
      },
      "code": {
        "prop": "iconOnly"
      }
    }
  },
  "remedy": "Review + append to contracts/button.contract.json props[], bump version, then npm run build && npm run figma:plan"
}

3 · Promotion — a human accepts the patch

Promotion is a reviewed edit to a JSON file in Git: the patch is applied to the contract’s props, and the version bumps (an added optional prop is a minor bump). This is not hypothetical — Button’s loading prop entered the system through exactly this door, v1.0.0 → v1.1.0, and ships today:

contracts/button.contract.json (v1.5.0) — the promoted prop as it ships; loaded at build time
{
  "name": "loading",
  "description": "Shows a spinning busy indicator beside the label while an async action resolves.",
  "type": "boolean",
  "default": false,
  "bindings": {
    "figma": {
      "kind": "BOOLEAN",
      "property": "Loading"
    },
    "code": {
      "prop": "loading"
    }
  }
}

4 · Regeneration — both surfaces, from the contract

npm run build re-emits every surface. To show precisely what the promotion changes, the site build ran the real emitters twice — once over the shipping contract, once over a reconstructed pre-promotion state (the shipping contract with loading and its spinner part removed at build time, and labeled as such). The diffs below are real emitter output against real emitter output.

The code surface

Button.tsx · 31 → 38 lines · +10 −3 — core react emitter, run twice at site-build time: contract without vs with the promoted prop
  import styles from './Button.module.css';
  
+ const ICONS: Record<string, string> = {+   "spinner": "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 20 20\" width=\"20\" height=\"20\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><path d=\"M 10 2.5 A 7.5 7.5 0 0 1 17.5 10\" stroke-linecap=\"round\"/></svg>",+ };+   export interface ButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {
    /** Visual prominence of the action. */
    /** Prevents interaction and communicates unavailability. */
    disabled?: boolean;
+   /** Shows a spinning busy indicator beside the label while an async action resolves. */+   loading?: boolean;  }
  
  /** Triggers an action or event. Use one primary button per context. */
  export const Button = forwardRef<HTMLButtonElement, ButtonProps>(function Button(
-   { variant = 'primary', size = 'md', disabled = false, className, children, ...rest },+   { variant = 'primary', size = 'md', disabled = false, loading = false, className, children, ...rest },    ref,
  ) {
    const classes = [styles.root, styles[`variant-${variant}`], styles[`size-${size}`], className].filter(Boolean).join(' ');
    return (
-     <button ref={ref} className={classes} disabled={disabled} {...rest}>-       <span className={styles.label}>{children}</span>+     <button ref={ref} className={classes} disabled={disabled} data-loading={loading || undefined} {...rest}>+       {loading ? (<span className={styles.loadingSpinner} aria-hidden="true" dangerouslySetInnerHTML={{ __html: ICONS["spinner"] }} />) : null}+ <span className={styles.label}>{children}</span>      </button>
    );
Button.module.css · 105 → 120 lines · +15 −0 — the generated CSS Module — the spinner’s rules appear; no handwritten style layer exists to update
  }
  
+ .loadingSpinner svg {+   width: 14px;+   height: 14px;+ }+ + .loadingSpinner {+   display: inline-flex;+   flex-shrink: 0;+   animation: ds-spin 0.8s linear infinite;+ }+ + @keyframes ds-spin {+   to { transform: rotate(360deg); }+ }+ 

The design surface

button.figma.js · 2130 → 2359 lines · +229 −0 — core figma emitter (the canvas sync script) — the Loading BOOLEAN property and spinner node appear in the same regeneration
          "property": "Disabled",
          "default": false
+       },+       {+         "property": "Loading",+         "default": false        }
      ],
            "children": [
              {
+               "type": "svg",+               "name": "loadingSpinner",+               "svg": "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 20 20\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"#FFFFFF\" stroke-width=\"2\"><path d=\"M 10 2.5 A 7.5 7.5 0 0 1 17.5 10\" stroke-linecap=\"round\"/></svg>",+               "svgPaintVar": "color/action/primary/foreground",+               "iconSize": 14,+               "visibleProp": "Loading",+               "visibleDefault": false+             },+             {                "type": "text",
                "name": "label",
            "children": [
              {
+               "type": "svg",+               "name": "loadingSpinner",+               "svg": "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 20 20\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"#FFFFFF\" stroke-width=\"2\"><path d=\"M 10 2.5 A 7.5 7.5 0 0 1 17.5 10\" stroke-linecap=\"round\"/></svg>",+               "svgPaintVar": "color/action/primary/foreground",+               "iconSize": 14,+               "visibleProp": "Loading",+               "visibleDefault": false+             },+             {                "type": "text",
                "name": "label",
  ⋯ 23 more hunks not shown

5 · The differ catches a surface that didn’t regenerate

Suppose the code was regenerated but the canvas was not — the property was promoted, and one surface lags. The differ names it. This finding is again verbatim output of the real differ at site-build time, over a scratch whose canvas snapshot lacks the promoted property:

parity/diff.ts over a scratch whose canvas snapshot is missing the Loading property — the exact mutation the eval detect-figma-missing-property locks
{
  "surface": "figma",
  "classification": "behind",
  "subject": "Button.Loading",
  "detail": "Contract prop \"loading\" has no BOOLEAN property on the Figma set",
  "remedy": "Add the property to the existing set via a scripted edit — sync scripts are currently CREATE-only and skip existing components (see docs/internal/figma-sync.md)"
}

Note the remedy string’s honesty: sync scripts are currently create-only, and the differ says so instead of promising an automation that doesn’t exist.

Standing receipts: the evals detect-code-added-prop, detect-figma-missing-property, and promotion-converges lock every step of this page, and the executed round-trip is the same loop run against the live canvas.