# Lean Design-Dev Handoff: Cutting AI Codegen Token Cost by an Order of Magnitude
Traditional design-dev handoff relies on screenshots and natural language, so agents re-parse, re-translate, and re-emit redundant styles every turn. This article starts from three root waste patterns, proposes a tool-agnostic Design-to-Code DSL stack and HCP tiering, and uses Figma Dev Mode + MCP + Code Connect as a validation instance.
A developer drops a design screenshot into Cursor with “implement this page using @corp/ui.” One pass burns 8K input and 5K output; two correction rounds add another 20K. The result is <div> soup until someone rewires it to the enterprise component library.
The bottleneck is usually not the model. It is that handoff has no reusable structured semantics—design outputs one-off visual facts, engineering re-translates every time, and the agent re-derives styles and component choices on every run. Using LLM cost fundamentals: bill = unit price × tokens × workflow amplification; handoff amplifies repeated work.
This article proposes a tool-agnostic lean design-dev handoff strategy: a Design-to-Code DSL stack targets three token waste roots; HCP (Handoff Context Pack) tiers DSL for agents. The body is abstract theory plus practice; Figma appears only at the end as a validation instance—Dev Mode + MCP + Code Connect show why the abstract approach cuts tokens.
Three root causes of token waste
Traditional handoff (screenshots, annotations, verbal specs) is expensive because three things are not reusable:
| Root waste | Traditional handoff | Token impact |
|---|---|---|
| ① Design-layer info not reusable | Colors, spacing, type live as pixels/hex in layers; tokens not bound to code semantics | Agent re-parses visuals or re-explains “which variable is 4px?” every turn |
| ② Repeated dev-side translation | No stable design component → @corp/ui mapping; every handoff re-aligns “which component, which props?” | Architecture re-explained each chat; output is divs first, refactor later |
| ③ Repeated redundant style generation | No global cache; one button change resends full page; agent re-emits existing layout/CSS | Full input resend; output duplicates known structure |
These compound: ① inflates input, ② inflates input and output, ③ makes incremental work look like full-page regen. A fourth pain—late validation (props wrong at CR)—follows from missing structure: no grammar, no compile-time checks, ×2–4 correction rounds.
Abstract fix: a reusable, cacheable, diffable Design-to-Code DSL stack:
① Non-reusable design info → Token Registry (design tokens bound to code syntax, globally cached)
② Repeated translation → Component Registry + Intent DSL (map once, hand off semantics)
③ Redundant regen → HCP tiering + Delta-First (L0/L1 cache, L3 sends only change)
Below, domain-specific languages (DSL) make these three mechanisms executable. DSL delivers high signal density, clarity, diffability, validation—context engineering applied to handoff.
Practice—same Hero CTA, natural language vs Intent DSL:
# Natural language (~150 tokens, drifts each turn)
"Primary large button, loading, label 'Get Started', use company Button,
import @corp/ui, track home_cta_click, i18n home.cta.start"
# Intent DSL (~40 tokens, deterministic, diffable)
component: ECR.corp.button
props: { variant: primary, size: lg, loading: false }
slots: { label: i18n:home.cta.start }
analytics: { event: home_cta_click }
Design-to-Code DSL Stack: the handoff domain language
Theory: handoff is not translation—it is a missing domain language stack. Five DSL layers address one or more waste roots; together they form UDHP (Universal Design Handoff Protocol).
Design ──► Handoff ──► AI gen ──► Verify
│ │ │ │
Component DSL Intent DSL Generation Verification
Architecture DSL │ DSL DSL
▼
HCP serialization
| DSL layer | Artifact | Waste addressed | Question |
|---|---|---|---|
| Component DSL | ECR Manifest | ② | Which code component? props/slots? |
| Architecture DSL | Architecture Profile | ② | Page templates, paths, cross-cutting rules? |
| Token Registry | Design token → code syntax | ① | Does primary map to var(--color-brand)? |
| Intent DSL | DID | ②③ | What should this screen implement? (diffable) |
| Generation DSL | Emission Profile | ②③ | How does ECR id become enterprise code? |
| Verification DSL | Contract Spec | Late validation | Is output compliant? |
Token Registry can live inside ECR or stand alone; the rule is tokens must bind code syntax, not just hex.
Agent role: from “vision + guess architecture + codegen” to parse Intent DSL → lookup Generation DSL → fill props.
Practice: minimal YAML skeleton:
tokens:
color.brand: { figma: "primary/500", code: "var(--color-brand)" }
space.16: { figma: "spacing/md", code: "var(--spacer-4)" }
components:
corp.button:
import: "@corp/ui/CorpButton"
props:
variant: { type: enum, values: [primary, default, text], default: default }
size: { type: enum, values: [sm, md, lg], default: md }
slots:
label: { type: i18n-key, required: true }
nodes:
- id: hero-cta
component: corp.button
props: { variant: primary, size: lg }
slots: { label: i18n:home.cta.start }
tokens: { background: color.brand }
emission:
components:
corp.button:
import: "import { CorpButton } from '@corp/ui'"
template: |
<CorpButton variant="{props.variant}" size="{props.size}">
{t('{slots.label}')}
</CorpButton>
contract:
nodeId: hero-cta
mustImport: ["@corp/ui"]
props: { variant: primary, size: lg }
HCP: lean transport for the three wastes
Theory: HCP is not another design format—it tiers DSL for agents, directly targeting ③ with ①② cache reuse.
In Harness Engineering: Token Registry + Component DSL = Guides; Verification DSL = Sensors; HCP = Lazy Loading for Context.
| HCP tier | Content | Waste | Transport |
|---|---|---|---|
| L0 | Token Registry + Component DSL summary | ①② global reuse | MCP Resource, content-addressed cache |
| L1 | Generation + Architecture DSL | ② project reuse | MCP Resource, project cache |
| L2 | Intent DSL subtree | ③ scope slice | inline, task nodes only |
| L3 | Delta + instruction | ③ incremental | inline, since → current diff |
Three rules: Reference-not-Inline (L0/L1); Scope Slicing (L2); Delta-First (L3).
Practice: HCP sketch + agent hook:
hcpVersion: "1.0"
cacheKeys: { l0: "ecr:sha256:a3f8...", l1: "profile:corp-web:v2" }
task: { scope: "node:hero-cta", since: "did:v3" }
context:
l0Ref: "ecr:sha256:a3f8..."
l1Ref: "profile:corp-web:v2"
l2Inline:
nodes:
- id: hero-cta
component: corp.button
props: { variant: primary, size: lg }
slots: { label: i18n:home.cta.start }
l3Inline:
delta: { props.size: { from: md, to: lg } }
async function implementFromDesign(task: Task) {
const ecr = await mcp.readResource(`ecr://${task.ecrHash}`);
const profile = await mcp.readResource(`profile://${task.projectId}`);
const hcp = await udhp.pack({ did: task.did, scope: task.scope, since: task.since });
const code = await agent.generate({ ecr, profile, ...hcp });
return udhp.verify({ code, contract: hcp.verification.contract });
}
Design-phase constraints: private architecture before handoff
Theory: “Use @corp/ui” in a prompt cannot stop freeform rectangles or div output. DSL grammar + design-time lint moves ② translation left.
| Mechanism | Waste | Practice |
|---|---|---|
| Constrained palette | ② | Designers only instantiate ECR components |
| Token → code syntax | ① | Tokens store code:, not hex |
| Props binding | ② | Picking props = writing an API call |
| Design-time lint | Late validation | udhp lint did.yaml --ecr ecr.yaml |
| Emission Profile | ②③ | Lookup, don’t guess imports |
Three gates: design lint → handoff lint → contract verify. Any failure stops before AI spend.
udhp ecr extract --from "./node_modules/@corp/ui/dist/index.d.ts" --out ./handoff/ecr.yaml
udhp emission init --from "./src/pages/Home/index.tsx" --ecr ./handoff/ecr.yaml --out ./handoff/emission.yaml
Token math: traditional vs DSL + HCP
| Mode | Approach | Total tokens | Three wastes |
|---|---|---|---|
| V1 Traditional | Screenshot + NL | 11K–28K/page | All three |
| V2 Semi-structured | Full JSON + arch notes | 5K–13K | ① partial |
| V3 DSL + HCP | L0/L1 cache + L2/L3 inline | 0.8K–3K/page | Targeted removal |
| V3 Incremental | L3 delta only | 80–250/run | ③ eliminated |
MVP: zero design-tool APIs
udhp ecr init --out ./handoff/ecr.yaml
udhp did import --asset ./design/home.png --map ./handoff/mapping.yaml --out ./handoff/did-home.yaml
udhp lint ./handoff/did-home.yaml --ecr ./handoff/ecr.yaml
udhp pack ./handoff/did-home.yaml --scope "node:hero-cta" --profile ./handoff/emission.yaml -o ./handoff/hcp.yaml
udhp verify --code ./src/pages/Home/HeroCta.tsx --contract ./handoff/contracts/hero-cta.contract.yaml
Designers annotate via a neutral mapping UI; tool adapters are P2.
Anti-patterns and action list
| Anti-pattern | Waste |
|---|---|
| Screenshot handoff | ①②③ |
| Tokens as hex only | ① |
| Re-explain component library each prompt | ② |
| One prop change, full page resend | ③ |
| Merge without contract verify | Late validation |
Five steps: extract ECR → Emission Profile → annotate one DID → lint → pack → verify → register L0/L1 on MCP.
Validation instance: Figma Dev Mode + MCP + Code Connect
Figma is not a prerequisite. It shows why its handoff mode beats traditional delivery on tokens—it implements the three abstract fixes inside one product.
| Abstract fix | Figma implementation | What it validates |
|---|---|---|
| ① Reusable tokens | Dev Mode Variables + code syntax: inspect shows var(--spacer-2) not 4px; tokens bound to code | Define once, reference in dev—no agent re-parsing pixels |
| ② Translate once | Code Connect: Figma instances map to production components; Dev Mode shows real imports and prop mappings | Translation at config time; handoff carries semantics |
| ③ Tiered context | MCP Server: get_metadata then scoped get_design_context; structured tree replaces screenshots | No full dump; Scope Slicing controls tokens |
Figma’s internal DS team reported the same for ①: before Dev Mode, devs guessed which CSS variable matched a style; now inspect shows var(--color-icon-onbrand) directly—repeat translation token cost → zero.
vs UDHP: Figma validates that all three fixes work and compose; it does not offer tool-agnostic Intent DSL, full HCP tiered cache, or open Generation DSL for private @corp/ui (MCP defaults to React+Tailwind). UDHP generalizes Figma’s pattern—any tool exporting raw assets can run the MVP; Figma users treat Code Connect as a Component DSL adapter and layer HCP tiering on MCP.
Close
Design-dev handoff token pain looks like “AI is expensive”; the roots are three non-reusable layers: tokens, component translation, generation context. The lean strategy is Design-to-Code DSL stack + HCP tiering so agents consume structure, not screenshots.
Theory: three wastes → three abstract mechanisms → five DSL layers + HCP.
Practice: extract ECR from @corp/ui, write Emission Profile, run lint → pack → verify, register L0/L1 on MCP. Figma Dev Mode + Code Connect + MCP is a worked example that the road is real—the protocol still belongs to your team.
References
- Anthropic, Effective context engineering for AI agents
- Figma, Guide to Dev Mode
- Figma, Figma MCP Server Developer Docs
- Figma, Code Connect Developer Docs
- Figma, How Figma’s Internal Design System Team uses Dev Mode