Turn a Claude Design handoff bundle into scaffolded React components, with provenance and dedup against the existing codebase.
After exporting a handoff bundle from claude.ai/design. This skill is the
entry point — it does NOT open a PR, run tests, or deploy. For the end-to-end flow (import → tests → PR), use
instead.
python
ARG = "$1" # First positional argument
if ARG.startswith("http://") or ARG.startswith("https://"):
bundle_source = "url"
bundle_input = ARG
elif Path(ARG).exists():
bundle_source = "file"
bundle_input = ARG
else:
AskUserQuestion(questions=[{
"question": "I couldn't resolve that as a URL or file. What is it?",
"header": "Bundle source",
"options": [
{"label": "Paste handoff URL", "description": "claude.ai/design URL"},
{"label": "Paste file path", "description": "Local handoff JSON"},
{"label": "Cancel", "description": "Abort import"}
],
"multiSelect": False
}])
Delegate to the orchestrator agent. The agent fetches, extracts the tarball, reads the README + chats, parses the HTML prototypes, and produces a normalized payload. Do NOT reimplement parsing here — the agent owns the (real, tarball-based) schema.
python
Agent(
subagent_type="claude-design-orchestrator",
description="Parse and normalize handoff bundle",
prompt=f"""Parse the Claude Design handoff bundle at {bundle_input}.
This is a gzipped tarball (NOT a JSON manifest). Layout:
<project>/README.md ← read first
<project>/chats/*.md ← read all (load-bearing)
<project>/project/*.html ← prototypes (may be absent if incomplete)
Tasks:
1. Fetch the bundle (WebFetch if URL → saved .bin path; Read if local file)
2. Extract: `tar -xzf <bin> -C /tmp/<scratch>/`
3. Read README.md, then every chats/*.md (intent + clarifications live here)
4. Compute bundle_id = sha256(canonical bundle URL or absolute path)
5. If project/ is MISSING → return status="incomplete" with the assistant's
last unanswered question; do NOT crash. Surface "what user should do".
6. If project/ exists → pick primary HTML:
- Prefer the file matching the URL's ?open_file= query param
- Else first alphabetical
7. From the primary HTML, extract:
- Inline `:root { --... }` CSS custom properties as design tokens
- Component sections (named via class/id/data-screen-label)
- Asset references (<link>, <img>) — keep as URLs, do not download
- EDITMODE JSON block (design-time state — capture as ANNOTATION only)
8. Produce normalized output payload (see agent spec)
9. Write provenance to .claude/design-handoffs/<bundle_id>.json:
- bundle_url, bundle_id, fetched_at, status, components: [], pr: null
10. Return the normalized payload as JSON
Surface any deviations from the expected tarball layout explicitly.
Never expect a JSON `components[]` field — that was the old (wrong) shape.
"""
)
Read the normalized
from the agent's payload.
python
if token_diff["conflicts"]:
AskUserQuestion(questions=[{
"question": f"Token conflict on {conflict.path}. Project says {conflict.project}, bundle says {conflict.bundle}. Resolve?",
"header": "Token conflict",
"options": [
{"label": "Keep project value", "description": "Bundle adapts to project"},
{"label": "Accept bundle value", "description": "Project adapts to bundle (writes new token)"},
{"label": "Both — namespace bundle's", "description": f"Add as {conflict.path}.imported"}
],
"multiSelect": False
}])
The agent already ran component-search per component. Read decisions from the normalized payload:
For each component with decision
or
, invoke design-to-code:
python
for component in payload["components"]:
if component["decision"] in ("scaffold", "adapt"):
# Compose, don't reimplement — design-to-code owns the EXTRACT/MATCH/ADAPT/RENDER pipeline
Agent(
subagent_type="frontend-ui-developer",
description=f"Scaffold {component['name']} from bundle",
prompt=f"""Use the design-to-code skill to scaffold this component.
Source: handoff bundle {payload['bundle_id']}
Component: {component['name']}
Target path: {component['target_path']}
Bundle scaffold seed:
```tsx
{component['tsx_scaffold']}
```
Resolved tokens: {component['tokens_resolved']}
Decision: {component['decision']}
{f"Adapt from: {component['existing_match']}" if component['decision'] == 'adapt' else ''}
Write the component, mirror existing project file structure, use project tokens.
"""
)