Design to code
How to keep your prototype and code in sync over time
Keep the prototype and code in sync: one source of truth per moment, chosen and shipped versions for each screen state, and captures of the running app.
To keep a prototype and code in sync, give each change one direction: the prototype leads while you explore, the code leads once something ships. Then record, for every screen state, which prototype version is chosen and which one is live, and refresh the prototype from the running app instead of from memory. The gap between chosen and live is your implementation backlog. Anything else is drift.
Why prototypes and code drift apart
Drift is rarely one mistake. It builds up from small, reasonable shortcuts:
- A bug fix changes a label or a margin in the code, and nobody updates the design.
- A design exploration is approved in a meeting but never built, and three months later it looks like the current screen.
- The code grows states the design never drew: a permission error, an empty search result, a very long name.
- Two people change the same screen in the same week, one in the design, one in the code.
After a few months, the prototype stops being a map of the app. People stop trusting it, and every new change starts with “what does this screen look like today?”
Coding agents speed this up in both directions. An agent can implement a screen in minutes, and it can also touch five screens while fixing one bug. Syncing has to be part of the loop, not a cleanup project.
Pick the source of truth for each moment
“Which one is the source of truth?” has two answers, depending on the moment. Write them down so nobody has to guess.
| Moment | Source of truth | What updates the other |
|---|---|---|
| Exploring a change | The prototype | Nothing yet: the code does not follow until a version is chosen |
| Implementing a chosen version | The prototype version you chose | The code, then a record that this version shipped |
| A fix made directly in the code | The code | A new capture or redraw of the affected states |
| Deciding what to build next | The difference between chosen and shipped | The backlog |
The rule that matters: a change flows one way at a time. If you edit the prototype and the code for the same screen at once, you get two versions of the truth and a merge by hand.
Track two versions per screen state: chosen and shipped
For each screen state, keep two pointers:
- Chosen: the prototype version you want in the product.
- Shipped: the prototype version the code implements today.
When they are equal, the state is in sync. When they differ, the state is waiting to be built, or someone changed their mind. Either way, it is visible.
Without a dedicated tool, a table in the repository is enough, for example docs/screens.md, updated in the same pull request as the code:
| Screen | State | Chosen | Shipped | Commit |
| -------- | ------- | ------ | ------- | ------- |
| Invoices | default | v4 | v4 | 3f2a91c |
| Invoices | empty | v2 | v1 | 8c04d17 |
| Invoices | error | v1 | v1 | 8c04d17 |
Here “Invoices · empty” is out of sync: v2 was chosen and not built yet. Ask your agent to update this table whenever it implements a state, and to list in its final report every screen and state its change affected. One line in your CLAUDE.md asking for that list makes silent UI changes much harder to miss.
Numbered versions are what make this work. If each change overwrites the previous design, there is nothing to point at. Why every prototype change should be a version goes deeper.
Refresh the prototype from the running app
When the code is the source of truth, update the prototype from what the app shows, not from what you remember. The most reliable way is a screenshot of each state, taken by a script at a fixed viewport, with demo data.
With Playwright, a minimal version looks like this:
import { chromium } from "playwright";
// One entry per state. How you reach the empty state depends on your app:
// here, a demo account with no invoice.
const states = [
{ name: "invoices-default", url: "http://localhost:3000/invoices" },
{ name: "invoices-empty", url: "http://localhost:3000/invoices?account=demo-empty" },
];
const browser = await chromium.launch();
const page = await browser.newPage({ viewport: { width: 1440, height: 900 } });
for (const s of states) {
await page.goto(s.url, { waitUntil: "networkidle" });
await page.screenshot({ path: `captures/${s.name}.png` });
}
await browser.close();
Keep the viewport the same as the prototype, so the two can be compared side by side. States behind a click, such as a modal or a menu, need a step before the screenshot. The Playwright screenshots guide covers full pages and single elements, and screenshot every screen of a web app with Playwright walks through sign-in and states.
Run every change through the same loop
Sync holds when every UI change, big or small, follows the same steps:
- Prototype the change as a new version of the affected states. Keep the old version.
- Choose the version to build. Choosing is a decision, not a side effect of the last edit.
- Implement the chosen version in the code, with your components and tokens.
- Run the checks and compare each state with its prototype at the same size.
- Record the chosen version as shipped, with the commit.
- Recapture the states that changed, so the next change starts from what really shipped.
Step 6 is the one teams skip. It is also the one that shows the difference between “implemented” and “implemented as designed”.
Changes that start in the code
Some changes will always start in the code: an urgent fix, a copy change, a new validation message. Do not block them. Close the loop afterwards:
- Ask the agent that made the change to list the screens and states it touched.
- Recapture those states, or redraw them from the code if the app cannot run.
- Mark the new reference as both chosen and shipped: it reflects the product, and nothing is waiting to be built.
If the fix contradicts a version that was chosen but not built yet, decide which one wins before the next implementation, not during it.
A sync checklist before each release
- Every state in the release has a chosen and a shipped version, and they are equal.
- States added in the code (errors, empty results) exist in the prototype too.
- Captures of the changed states are newer than the last UI commit.
- Old explorations that will not ship are marked as such, not left looking current.
- The agent’s reports for the release list the states they touched.
How agentappbuilder keeps track
agentappbuilder builds this loop into the canvas. Each prototype frame keeps every version, with an active version (the one you chose) and an implemented version (the one your agent last reported as coded). A small dot on the canvas marks a state whose active version differs from its implemented one. When you say “apply”, your agent codes those states, runs your app’s checks and reports what it implemented.
If you make another version active on a state that is already implemented, the platform opens a notice, and at its next request your agent asks you whether production should follow. To refresh from the code, your agent captures the running app with a Playwright script on your machine, with your agreement and demo data. You import the captures into the prototype, where they become versions that are active and implemented at once. Concepts defines each of these, and the canvas shows where they appear.
Summary
A prototype stays in sync with the code when changes flow one way at a time, when every screen state has a chosen and a shipped version you can compare, and when the prototype is refreshed from the running app after each release. The mechanics are simple. What keeps them working is running them for every change, including the small ones that start in the code.
Frequently asked questions
Why do prototypes and code get out of sync?
Changes happen in two places. Fixes made directly in the code never reach the design, and explorations in the design look as if they shipped. Without a record of which version is live, nobody can tell.
Should the prototype or the code be the source of truth?
Both, at different moments. The prototype leads while you explore a change, and the code is the truth for what is live. Record which prototype version each screen state ships.
How do I update a prototype after changes in the code?
Capture the running app, one screenshot per screen state, at a fixed viewport and with demo data, and use it as the new reference. Redrawing from memory brings back the drift you wanted to remove.
Try it on your own app
Tell Claude Code one sentence. It creates the project and gives you a link to see your screens.
Tell Claude Code:
Connect this project to agentappbuilder.com and show me its screens.