design-to-code pages
- design-to-code
- From pages to components
From pages to components
Confirm and capture a baseline of the kept pages, seed the list of components from markers, and run the local gates on a library.
This page continues from Getting started, with Homepage.html kept and Homepage v2.html discarded. It covers the three stages around the extraction: the baseline, the list of components, and the checks on the finished library.
Confirm and capture a baseline
A baseline is a set of screenshots of the kept pages, the reference every later visual check compares against. design-to-code writes one only for a page a person has confirmed, and only when two fresh captures of it agree pixel for pixel.
Capture needs the screen sizes to use, so add viewports to the project file first:
{
"slug": "homepage",
"source": { "dir": "design/homepage" },
"pages": { "discard": ["Homepage v2.html"] },
"viewports": [
{ "name": "mobile", "width": 375, "height": 667 },
{ "name": "desktop", "width": 1280, "height": 800 }
],
"tolerance": { "designVsCode": 0.01 }
}
tolerance.designVsCode is the share of pixels, from 0 to 1, that may differ between the design and your code in later checks.
Then confirm, then capture:
D2C_PROJECT=homepage bun run d2c baseline confirm
D2C_PROJECT=homepage bun run d2c baseline capture
baseline confirm opens each kept page in a browser window, shows what serve --check found on it, and asks Does <page> work as in Claude Design? [y/N]. With source.shareLink set to the design's share link, it first compares each page with the design's own view, and confirms a page on its own when under 1% of pixels differ. [verify: baseline confirm was not run for this page; it needs a person at a browser window.]
baseline capture refuses a page nobody confirmed:
$ bun bin/d2c.ts baseline capture
d2c baseline: 1 of 1 kept pages have no confirmation for the current import: Homepage.html; run `bun run d2c baseline confirm` first
error: script "d2c" exited with code 1
A page whose two captures differ, such as one with a live clock, is marked flaky with the region that changed, and no screenshot is kept for it. The command exits 1; make that part of the demo hold still, then capture again.
Seed the list of components
The seed manifest lists each component to build: its level, the page and CSS selector where it sits, its states, its variants and any documentation text. Components are ordered smallest level first, so everything a component contains comes before it.
The fastest path is an export that marks its components with data-* attributes. This is the kept page from the fixture:
<!doctype html>
<html><head><meta charset="utf-8"><title>Homepage</title>
<style>:root{--brand:#c0392b;--ink:#222}.btn{padding:8px 16px;background:var(--brand);color:#fff;border:0}</style></head>
<body>
<header data-component="SiteHeader" data-level="organism">
<span data-component="Logo" data-level="atom">Acme</span>
<button class="btn" data-component="Button" data-level="atom" data-variant-size="md">Sign up</button>
</header>
<p data-doc="Button">A button starts the one action a section offers.</p>
</body></html>
D2C_PROJECT=homepage bun run d2c seed
$ bun bin/d2c.ts seed
d2c seed: 3 entries, 0 rejected; wrote design/homepage/seed-manifest.json and design/homepage/seed-rejected.json
The first entry of seed-manifest.json:
{
"name": "Button",
"level": "atom",
"page": "Homepage.html",
"selector": "html > body > header > button",
"examples": [],
"states": [],
"variantAxes": {
"size": [
"md"
]
},
"parts": [],
"docText": [
"A button starts the one action a section offers."
],
"origin": "markers"
}
Every candidate is checked against the served page before it is written: its selector must match exactly one element, and a component may not contain another of the same or a higher level. A candidate that fails goes to seed-rejected.json with every reason that applies.
A kept page with no markers, and no filled _ds_manifest.json in the export's _ds/ folder, sends seed down the agent path: one Claude session proposes candidates, and each is checked the same way. That session counts against your Claude usage. It was not run for this page.
Build the components
No verb builds the components yet: bun run d2c --help ends with "No extraction verb is implemented yet." Until one does, you write the library in target.dir yourself, by hand or with an agent of your own, in the order seed-manifest.json gives.
The repository's templates/library/ folder is the starting package the README describes for every library. It holds a package.json, a tsconfig.json, the ESLint config the build-lint gate applies, and class-name helpers in src/lib/. The gates look for each component in a folder named after it, inside a level folder under src. The level folders are the names in levels, so Button belongs in src/atoms/Button/, with its stories in Button.stories.tsx.
Coming, in an open spec (design-to-code#16, the extraction plan): a planned d2c plan --format=rafa would print a rafa plan from the seed manifest, so that a rafa loop builds one component per session. It is not built.
Check the library
gates --local checks the library in target.dir, by default components/<slug>, before anyone reviews it. Four gates run in order, and each runs even when an earlier one fails:
| Gate | What it checks |
|---|---|
structure | imports between levels, shared variants, layout components taking children only, no pages/ folder |
state-coverage | every state the seed manifest names has a story |
build-lint | the TypeScript check and ESLint with the library's own config |
story-health | every Storybook story renders without an error |
Each gate writes one result file per component in <target.dir>/gate-results/, and the command exits 1 when any gate fails. --local is required, because the gates meant for CI are not built yet:
$ bun bin/d2c.ts gates
d2c gates: --local is required: only the local gates run so far
…
error: script "d2c" exited with code 1
Run over a copy of templates/library/ with no components in it yet, every gate fails, and the summary names all four:
$ bun bin/d2c.ts gates --local
d2c gates: structure fail (1 of 1 result failed, 1 finding, 0 files scanned)
d2c gates: state-coverage fail (4 of 4 results failed, 4 findings, 0 files scanned)
d2c gates: build-lint fail (1 of 1 result failed, 1 finding, 0 files scanned)
d2c gates: story-health fail (1 of 1 result failed, 1 finding, 0 files scanned)
d2c gates: 4 of 4 gates failed: structure, state-coverage, build-lint, story-health
error: script "d2c" exited with code 1
gate-results/Button/state-coverage.json then says why: "the seed manifest names Button, but no level folder under src holds a Button folder".
[verify: the gates were not run on a library with components in it; this run shows the order, the result files and the exit code only.]