Calipers

← The sequence

Write the contract before you write the checker

01of 09starts here766 wordsdesign/contract.mjs

The failure it prevents

Someone widens a container from 1200 to 1180 to fix one page. Nothing fails, because the number lives in three places and no check knows what it is supposed to be. Six weeks later half the site is at 1180, half at 1200, and the design system is now whatever the last commit said. Every layout check you could write is blocked on this: there is nothing to compare rendered pixels against.

The prompt

You are going to extract this project's design rules into one machine-readable contract that automated checks can import. **Do not write any checks in this task.** The contract is the deliverable.

**Step 1 — find where the rules are written today.** Read, in this order: README / CONTRIBUTING / AGENTS.md / CLAUDE.md / any docs/ design or style guide; the Tailwind or theme config; the global stylesheet's custom properties; and the ten most-imported layout components (find them by grepping imports, not by guessing). Collect every rule stated as a number, an exact string, or an absolute — "the content column is 1200px", "radius is 12px everywhere", "body copy never exceeds 72ch", "only transform and opacity animate", "one h1 per page".

**Step 2 — sort what you found into three buckets and print the table before writing any file.**
- MEASURABLE — has a number or an exact string, and a rendered page either matches it or does not. Goes in the contract.
- JUDGEMENT — "feels balanced", "reads premium", "generous whitespace". Does NOT go in the contract. Write these to `design/not-checkable.md` with one line each so nobody re-litigates them into the audit later.
- AMBIGUOUS — stated, but with unlisted exceptions: "cards are square" when the sidebar cards obviously are not. **Stop and ask me one question per item.** Do not guess the exception list; a guessed exception becomes a rule that fires on correct code, and a check that fires on correct code gets muted within a week.

**Step 3 — write `design/contract.mjs`.** Plain data. No logic, no branching, no imports of application code. Every entry carries the value, a one-line `why`, and a `source` pointing at the file and line the rule was written in:

```js
export const CONTRACT = {
  readingColumns: { value: 1, why: "all running text starts at one left edge; media may be wider", source: "docs/design.md:41" },
  contentWidth:   { value: 1200, why: "the column every text block resolves to", source: "docs/design.md:44" },
  cardRadius:     { value: 12, why: "one radius across the product", source: "tailwind.config.ts:31" },
  clsBudget:      { value: 0.01, why: "entrances are transform/opacity only, so anything above noise is a real reflow", source: "docs/design.md:70" },
  retired:        { value: ["1040px", "830x470"], why: "pre-2024 column widths; grep prose for these", source: "git log" },
};
```

**Step 4 — bind the contract to where the app actually holds the number.** For each entry whose value also exists in the app (a CSS custom property, a theme key, a constant), add `binding: { file, declaration }`. This is what later lets one check assert the app and the contract still agree — and it is the only honest answer to "which one is the source of truth", because there are always two.

**Step 5 — rewrite mechanism rules as the invariant they exist to produce.** This is the most important instruction here. "Use the `<Container>` component" is a mechanism; the invariant is "running text starts at one left edge." Check the invariant. A check that asserts the right class name passes a page where two nested containers compounded their padding and shoved a paragraph 16px right — it looks correct in source and wrong on screen. Every entry in the contract must be phrased as something a browser can measure about the rendered page, or as an exact string a parser can find in a file. If you cannot phrase it that way, it belongs in `not-checkable.md`.

**Step 6 — name the exemption channels now, in the contract, before any check exists.** Declare the attribute a component uses to say "I overflow on purpose", the attribute that marks a component which is already its own frame, and the path prefixes for vendored or generated code that checks must downgrade rather than fail. Exemptions invented later, at the call site, under pressure to make a red build green, are how audits die.

**Step 7 — write `design/contract.test.mjs`.** One test: grep the checker directory for every top-level key in CONTRACT and fail if a key is referenced by zero checkers. An entry nobody imports is a rule nobody enforces, and it will read as enforced to the next person who opens the file.

**Hand back:** the three-bucket table, the questions from the AMBIGUOUS bucket (do not proceed past them on your own), the contract file, and the output of `node --test design/contract.test.mjs`. If fewer than five rules landed in MEASURABLE, say so plainly — this project may not have a written design system yet, and the honest next step is to write one, not to invent one from the code and present it as discovered.

What it produces

design/contract.mjs (values + why + source + bindings + declared exemption channels), design/not-checkable.md, design/contract.test.mjs, and a printed MEASURABLE / JUDGEMENT / AMBIGUOUS triage table.

How you prove it works

Change one bound number in the app only — bump the container custom property from 1200px to 1180px in the stylesheet, leave the contract alone. The binding check must fail naming both lines (contract entry and CSS declaration), not just "layout wrong". Then the reverse: delete a key from CONTRACT that a checker imports and confirm the checker fails loudly with an undefined value rather than quietly skipping the rule. Then add a decorative key nothing imports and confirm contract.test.mjs reports it as unenforced.