18px means 18px
What happened when Claude Code and Claude Design handed work back and forth, and how we kept the build faithful to its prototype, down to the pixel.
Field notes from a real project. The app and the protagonist's name are made up; the lessons aren't. The two agents are real, though. Claude Design keeps a clickable prototype, Claude Code keeps the repo, and they hand work to each other: Claude Code uploads a brief into Claude Design, Claude Design sends back a changelog, Claude Code builds it.
Moha is a backend engineer. She says "I can't design" the way some people say "I can't cook." Not strictly true, but true enough that she's never enjoyed it. So when she started building Cellar, a self-hosted tracker that marks her wine collection to a public price index and tells her what it's worth today, she did something that felt a little ridiculous: she put two AI agents to work and let them talk to each other.
One of them was Claude Design, which kept a real, clickable prototype of the app. The other was Claude Code, which had the repo. The two didn't route through her; they routed through each other. Claude Code would write up what a screen needed and upload that brief straight into Claude Design. Claude Design would chew on it and send back a changelog, a short, exact account of what to build. Claude Code would build it. Moha's job was neither the design nor the code. It was keeping that loop honest, and the loop is where every interesting failure lived.
"Build this" is not a spec
The first feature was the dashboard: a big net-worth number, a sortable table of bottles. Early on, before the loop had any rules, Moha just screenshotted the prototype, handed it to Claude Code, and said build this. It built something. The something looked like the screenshot. It was also wrong in a dozen ways you'd never catch at a glance:
- font-size: 14px; /* the prototype said 14px */+ font-size: 16px; /* "16 feels more balanced" */ - "Drink soon" /* the exact label */+ "Ready to drink" /* reworded for "clarity" */ - <span class="chip"> /* the design system's name */+ <span class="badge"> /* renamed to be "more semantic" */Here's the whole thing in one line:
An LLM implementer will confidently improve your design unless you tell it the values are binding.
Ask one to "match this mockup" and it paraphrases. It rounds 18px up to a tidier 20px. It rewrites a label so it reads "better." It renames a class to something "more semantic." Moha had assumed the hard part of design-to-code was the visual translation. It was the opposite: talking the implementer out of its own taste.
The changelog
So they stopped trading screenshots. Now Claude Code wrote a brief (what the screen had to do, and why) and uploaded it into Claude Design. Claude Design sent back a changelog: one small entry per change, and it read less like a mockup than like a contract.
# 0006 · Bottle row: tags + drink-window chip
Touches: styles.css (.bottle-tags, .chip), BottleRow.tsx
Copy (verbatim): "Drink soon", "Cellar", "Past peak"
Chip: height 20px · 11px / 600 · radius 6px · padding 0 8px
Place: under the bottle name, margin-left: 49px
Color: a per-tag hue from a fixed 6-swatch palette,
never the gain/loss green or red.A couple of rules rode along with it, written into the repo's own instructions so Claude Code couldn't quietly forget them:
## Design-fidelity (binding)
1. Concrete values are binding (px, weights, hex, class
names, copy, icons), implemented verbatim. Disagree? Ask.
2. Build a spec-line ledger before coding; tick each value
off the diff after. The ledger goes in the PR.
3. Surface conflicts: never resolve an ambiguous or
self-contradictory changelog. Halt and ask.
4. Verify the changed STATE, not "it renders": screenshot the
open editor / hover / edge case, beside the design.The ledger felt like pointless bookkeeping for about a day. Then it caught a chip background that had drifted from 13% to 16% opacity, something no reviewer was ever going to clock by eye, and it stopped feeling pointless.
spec-line ledger / changelog 0006 impl?
──────────────────────────────────────── ────
chip height 20px ✓
chip font 11px / 600 ✓
chip radius 6px ✓
copy "Drink soon" (verbatim) ✓
margin-left 49px ✓
palette hue (not gain/loss color) ✓ ← caught a 13%→16% bg slipTwo languages for one button
Cellar's filter row reused a component from the dashboard. In the prototype it was called .chip. Six weeks of repo history called the same thing .badge. Neither was wrong; they were just two dialects for one object, and nothing ever broke to flag it. The code worked. The prototype worked. They drifted apart in Moha's head until she couldn't hold both at once, and one whole changelog went to nothing but reconciling names.
The rule that came out of it: every changelog calls out a new class name against the names that already exist, so it gets adopted or argued with before it ships. One dictionary, not two.
"It renders" is a lie the screenshot tells you
Moha approved a PR she shouldn't have. The changelog had asked for an inline tag editor: chips in a field, an autocomplete, Save and Cancel. The screenshot on the PR showed the table looking great, so she merged. The bug lived in a state the screenshot never captured: open the editor on a one-row table and the dropdown got sheared clean off by an overflow: hidden two components away. The resting state was perfect. The interesting state was broken.
New rule, the one she'd staple to a junior's monitor: check the state, not the render. Open the editor. Hover the row. Filter down to the one awkward case. Screenshot that, next to the prototype's version of the same thing. Resting-state screenshots are where bugs hide.
When the changelog argues with itself
Then a changelog contradicted itself: one line called a rule "unchanged," the next gave it a new value. Claude Code, told to take values literally, now had two truths and no way to honour both. The tempting move is to let it choose. It'll pick something plausible, and you'll never find out a decision got made at all.
So the rule grew a clause: surface the conflict; don't resolve it quietly. An agent that stops and asks is safer than one that picks, because the stop is visible and the pick isn't.
The choice the changelog handed back
The least obvious lesson: even a binding-values regime still has real decisions in it, and the agent must not be the one making them. A changelog for tag colours came back with two options (a quiet per-tag palette, or a flat neutral grey) and said, in so many words, pick one. Claude Code could have picked. It nearly did.
But which of the two ships isn't an implementation detail. It's a product call with Moha's name on it. So: when a changelog hands back a choice, hand it to the human, with a recommendation, not a shrug. She got the two options, a preview of each, and a nudge toward one, and decided in about four seconds. The agent's job was to set the decision up, not to make it.
Paint needs a wall first
Tags had to exist before they could look like anything. There's no point writing a changelog for a chip when the API doesn't return tags and the table can't store them. So the work split in two: a backend change first (migration, endpoint, the field riding the payload, no UI at all), then the visual changelog on top of it afterwards. The two halves could even run at once, Claude Design drafting the look while Claude Code laid the plumbing, meeting in the middle. It kept the reviews honest, too: backend gets read for correctness, UI for fidelity. Bolt them together and the review does neither properly.
The changelog that moved
This one almost got her. Moha told Claude Code to build changelog 0029. She'd looked a week earlier and 0029 was a privacy screen. But these things are alive: in the days since, the privacy screen had been folded into 0028, and 0029 had been renumbered into something completely different. If Claude Code had trusted the number in her head, it would have built the wrong screen, confidently, start to finish.
It didn't, thanks to a habit that turned out to carry more weight than it looked: list the directory and check the real filename before opening anything. When your design source of truth is a living document another agent keeps editing, the index drifts too, not just the values inside it.
The cadence
By about the tenth feature the whole thing had settled into a loop, and the loop barely needed Moha, except at the two points where it genuinely did.
The handoff had stopped being a handoff. A handoff is the moment something gets dropped on the floor. This was a contract: written down, versioned, checkable. The human stood exactly where a human should: at the decisions, and at the merge.
The playbook (steal this)
If you're wiring a design agent to a coding agent (Claude Design and Claude Code, in our case, though the same goes for a designer and a developer), here's the short version:
- Don't send screenshots, send contracts. A changelog full of concrete values beats "make it match" every single time.
- Treat the values as binding, and keep a ledger. Pull every px, weight, hex, class and string out up front, then tick them off against the diff.
- Talk the implementer out of its taste. The most common failure isn't a bug, it's a confident, reasonable-sounding "improvement." Ban it on purpose.
- One vocabulary. Flag every new class name against the ones you already have, or you'll run a translation layer in your head forever.
- Check the state, not the render. Open the panel, the editor, the ugly one-row case, and screenshot that, beside the design.
- Surface conflicts; never resolve them quietly. A question is visible. A quiet guess is not.
- Send real decisions back to the human, with a recommendation. Binding values still leave genuine product calls.
- Backend before brushwork. Split the data work out from the visual changelog; run them in parallel if you can.
- The index drifts too. Re-check the current changelog before you build it; never trust a number you remember.
- Keep the human at the decisions and the merge, and nowhere else. That's where judgement compounds; the rest is where automation does.
Moha still says she can't design. But Cellar looks exactly like its prototype, down to the 18px, because at some point she stopped trying to be a designer and became something more useful and harder to name: the person who keeps a clean wire between two things that would otherwise drift apart. And 18px, in the end, means 18px.