After three sprints of code-side iteration, the Figma file describes an app that no longer exists — and every new design starts from fiction. Code-to-canvas handback captures the live, rendered UI back into editable Figma layers, so designers design against what actually shipped.
Tier: Advanced · Workflow area: Design-to-code & design systems · Tools: Claude Code, Figma (generate_figma_design)
Design-to-code gets all the attention, but drift flows the other way too: engineers tweak spacing during bug fixes, product tunes copy in code, states get added that no frame ever showed. The design file silently becomes archaeology. This playbook establishes the reverse pipeline — code back to canvas — as a routine sync step, not a heroic quarterly cleanup.
Pick the sync set and freeze the capture conditions. List the surfaces whose canonical truth is now code — e.g., the settings area after an accessibility polish sprint. Capture like pb-11: deterministic data (MSW fixtures), fonts loaded, animations off, agreed viewport (1280 for desktop surfaces). One state list per surface: default, plus any state designers will need to edit (empty, error, upgraded-plan).
Wire the agent to Figma's design-generation MCP and confirm the target file. With the Figma MCP connected (Dev Mode MCP server, pb-09) the agent gets generate_figma_design; create or open the target file — a dedicated "As-Built — <app> <release>" page, never the working design file's canvas:
claude mcp add --transport http figma-dev-mode http://127.0.0.1:3845/mcp
claude
Confirm you can reach the Figma MCP and list the tools available.
We will generate into the file "As-Built — Settings v3.4", page
"2026-08 sync". Do not modify any other page.
Generate Figma frames from the live UI, one surface at a time. Point the agent at the running app plus the component source — source access is what lifts output from screenshot-tracing to structured layers:
Open http://localhost:3000/settings/profile (MSW scenario: default).
Using generate_figma_design, recreate this screen as an editable
Figma frame on the "2026-08 sync" page:
- Auto-layout frames mirroring the component structure in
src/features/settings/ProfileSection.tsx (read it — name layers
after the components: ProfileSection/AvatarRow/Field...)
- Text styles and fills bound to our Figma variables where they
exist (collection "acme/semantic"); flag any code token with no
Figma variable in a text note beside the frame
- Frame name: "settings-profile / default / v3.4"
Then repeat for MSW scenarios: empty, server-error.
Good output: frames with real auto-layout (drag a card — siblings reflow), semantic layer names, variable-bound fills — not one flat rasterized group. Spot-drag two elements in Figma to verify editability before generating the next surface.
Run the drift review — the actual payoff. Put old design frames beside the as-built frames and make a multimodal pass produce the drift table (pb-11 mechanics, reversed roles):
Image 1: the design file's settings-profile frame (last edited
2026-03). Image 2: the as-built frame generated today. Table of
drifts: element | property | designed | shipped | likely reason
(bug-fix / a11y / copy tune / unknown). Mark "unknown" rows — those
need a human decision on which side is right.
Triage with the designer in one sitting: shipped-wins rows update the working design file (designer copies from as-built), design-wins rows become frontend tickets, unknowns get decided. Every row leaves with an owner.
Institutionalize the cadence. Add handback to the release ritual: each release cut regenerates the as-built page (new page per release, so history persists), and the drift review is a standing 30-minute design/eng sync. Script the capture half so the cost stays near zero:
sh scripts/asbuilt-sync.sh settings v3.4
The script boots the app with fixtures, walks the surface/state list, and drives the agent through step 3's prompts per entry.
Feed the design system both ways. Recurring drift on the same component (the Badge that's been 4px tighter in code for six months) is a parity signal: fix the Figma library component or the coded one — and if you run Code Connect (pb-10), update the mapping in the same PR so future design-to-code runs inherit the resolution.
Handback output is a communication artifact, not a design-system source. Designers copy from it, measure it, and decide against it — but library components stay maintained by hand in the library file. Treating generated frames as library masters imports code-side accidents into the system as if they were decisions.
Design-to-code without code-to-canvas is a one-way street that ends in fiction. Regenerate an as-built page each release, run the drift table with the designer, and route every divergence to an owner — that loop is what keeps the Figma file a map instead of a memory.