From rune
Generate prototype React components + Storybook stories from Figma URLs. 6-phase pipeline: figma_to_react → UntitledUI matching → prototype synthesis → verify → storybook integration → present. Input: Figma URL(s). Output: prototypes auto-copied to Storybook with full-screen preview opened in browser. Generates both individual components AND a full-page composition for complete screen preview. Use when you want to preview design implementation before coding. Trigger keywords: prototype, figma prototype, storybook from figma, design preview, generate components from figma, preview design. <example> user: "/rune:design-prototype https://www.figma.com/design/abc123/MyApp?node-id=1-3" assistant: "Generating prototypes from Figma design..." </example> <example> user: "/rune:design-prototype --describe 'login form with email and social login'" assistant: "Generating prototype from description..." </example>
npx claudepluginhub vinhnxv/rune --plugin runeThis skill is limited to using the following tools:
**Runtime context** (preprocessor snapshot):
Generates production-ready React components from screenshots, descriptions, or URLs via Stitch extraction, 21st.dev matching, Storybook checks, and design token adaptation.
Generates interactive UI prototypes in frontend repositories from PRD and User Journey to validate interaction flows, states, and navigation before HLD/API contracts.
Design interactive prototypes and user flows using Figma, Framer, ProtoPie. Compare tools, create flows with TypeScript templates, implement testing strategies, and manage dev handoff to validate ideas before coding.
Share bugs, ideas, or general feedback.
Runtime context (preprocessor snapshot):
find tmp -maxdepth 1 -name '.rune-*-*.json' -exec grep -l '"running"' {} + 2>/dev/null | wc -l | tr -d ' 'git branch --show-current 2>/dev/null || echo "unknown"Standalone prototype generator: extracts Figma designs, matches against UI library components, and synthesizes prototype React components with Storybook stories.
Load skills: frontend-design-patterns, figma-to-react, design-system-discovery, storybook, context-weaving, rune-orchestration, team-sdk, polling-guard, zsh-compat
/rune:design-prototype <figma-url> # Full pipeline from Figma URL
/rune:design-prototype <url1> <url2> # Multiple Figma URLs
/rune:design-prototype --describe "login form with social" # Text-only mode (library search)
/rune:design-prototype <url> --no-storybook # Skip Storybook story generation
/rune:design-prototype <url> --components 5 # Limit to top N components
/rune:design-prototype <url> --no-team # Force single-agent mode
| Flag | Default | Description |
|---|---|---|
--components N | 5 | Max components to extract from Figma |
--no-storybook | false | Skip Storybook story generation |
--describe 'text' | — | Text-only mode: skip Figma extraction, search library by description |
--no-team | false | Force single-agent mode (no Agent Team even for >= 3 components) |
true in talisman.yml (shared gate with design-sync).mcp.json (for URL mode)Phase 0: Validate Input + 3-Layer Detection
→ Parse ARGUMENTS, detect Figma URL vs text description
→ Check design_sync.enabled gate
→ Phase 0a (parallel): L1 discoverFrontendStack() + L3 discoverUIBuilder()
→ Phase 0b (sequential, URL mode): L2 discoverFigmaFramework()
→ Phase 0c: Read brand config (readTalismanSection('brand')), inject brand.typography into DesignContext
→ Compose DesignContext → Write design-context.yaml
→ Create output directory
|
Phase 1: Extract (URL mode only)
→ figma_list_components per URL
→ figma_to_react per component (capped by --components)
→ Save tokens-snapshot.json + extraction reports
|
Phase 2: Match (conditional — requires UI builder MCP)
→ Search builder library for each extracted/described component
→ Circuit breaker: 3 consecutive failures → skip remaining
→ Write match-report.json with scores + confidence
|
Phase 3: Synthesize
→ Combine figma-ref + library-match into prototype.tsx
→ Generate Storybook stories (unless --no-storybook)
→ Write per-component output to design-references/
|
Phase 3.5: UX Flow Mapping (conditional — >= 2 components)
→ Analyze inter-component relationships
→ Generate flow-map.md with navigation + data flow
|
Phase 4: Verify (conditional — >= 1 prototype generated)
→ Structural self-review of generated prototypes
→ Check import consistency, prop types, story coverage
|
Phase 4.5: Storybook Integration (auto-copy + launch)
→ Copy prototypes to storybook/src/prototypes/
→ Install deps, launch Storybook dev server
→ Detect full-page composition, open in browser
|
Phase 5: Present
→ Aggregate reports into summary
→ AskUserQuestion with next-step options
Parse $ARGUMENTS to determine input mode, then run the 3-layer detection pipeline to
build a DesignContext that drives Phases 2-3. Detection has a combined timeout budget
of detection_timeout_ms (default: 5000ms) — see ARCH-004.
Input modes:
--describe flag present → Layer 1 + Layer 3 only (ARCH-005) → Phases 2-5AskUserQuestion("Provide a Figma URL or use --describe 'text'")// Guard empty --describe (BACK-005) + harden input (SEC-005)
if flags.describe !== undefined:
if (!flags.describe || flags.describe.trim().length === 0):
AskUserQuestion("--describe requires a non-empty description. Example: --describe 'login form with email and social login'")
STOP
flags.describe = flags.describe.replace(/<[^>]*>/g, '').slice(0, 500) // strip HTML, cap at 500 chars
talisman = readTalismanSection("settings")
if NOT talisman?.design_sync?.enabled:
AskUserQuestion("design_sync.enabled is false. Enable it in talisman.yml to use this skill.")
STOP
mode = flags.describe ? "describe" : "url"
// --- 3-Layer Detection Pipeline ---
// ARCH-001: L1 + L3 run in parallel (local file reads only, ~2-3 tool calls)
// ARCH-004: Combined timeout budget for all 3 layers
detection_timeout_ms = talisman?.design_prototype?.detection_timeout_ms ?? 5000
startTime = now()
// Phase 0a: Parallel — L1 (frontend stack) + L3 (builder MCP) + Figma fetch
[frontendStack, builderMCP] = parallel(
discoverFrontendStack(repoRoot), // Layer 1: ~2 tool calls
discoverUIBuilder(sessionCacheDir, repoRoot, null, null) // Layer 3: ~1 tool call
)
// Phase 0b: Sequential — L2 (Figma framework, URL mode only)
figmaFramework = null
if mode === "url":
// Fetch Figma API data (can overlap with L1+L3 in parallel)
figmaApiResponse = figma_list_components(figmaUrls[0])
nodeId = extractNodeId(figmaUrls[0])
if (now() - startTime) < detection_timeout_ms:
figmaFramework = discoverFigmaFramework(figmaApiResponse, nodeId) // Layer 2: 0 tool calls
// Re-run L3 with Figma framework for better builder matching
if figmaFramework is not null AND figmaFramework.score >= 0.40:
builderMCP = discoverUIBuilder(sessionCacheDir, repoRoot,
frontendStack.detectedLibrary, figmaFramework)
// ARCH-005: Text mode skips Layer 2 entirely — figmaFramework stays null
// Compose unified context (see design-context.md)
if (now() - startTime) >= detection_timeout_ms:
designContext = { synthesis_strategy: "tailwind", rationale: "Detection timeout exceeded" }
else:
designContext = composeDesignContext(frontendStack, figmaFramework, builderMCP, mode)
// BACK-008: Cache key includes figma_node_id for per-URL caching
cacheKey = mode === "url" ? (sessionId + ":" + nodeId) : sessionId
timestamp = formatTimestamp()
outputDir = "design-references/{timestamp}"
Bash("mkdir -p {outputDir}")
Write("{outputDir}/design-context.yaml", designContext) // Persist for Phase 2/3
maxComponents = flags.components ?? talisman?.design_sync?.max_reference_components ?? 5
// Query past design decisions before prototype generation
try {
const pastDesign = mcp__plugin_rune_echo_search__echo_search({
query: `${componentName} design tokens color typography layout`,
limit: 3
})
if (pastDesign.results?.length > 0) {
designContext.past_decisions = pastDesign.results.map(r => r.content)
}
} catch (e) {
// Non-blocking — echo-search MCP may be unavailable
designContext.past_decisions = []
}
Tool call budget (BACK-009): Phase 0 costs ~4-6 tool calls total:
See pipeline-phases.md for detailed Phase 0 pseudocode and detection orchestration.
See design-context.md for the composeDesignContext() algorithm and decision matrix.
Runs only in URL mode. For each Figma URL:
figma_list_components(url) to discover top-level frames/componentsmaxComponents (sorted by visual hierarchy)figma_to_react(nodeId) per component → reference JSX + Tailwind{outputDir}/extractions/{component-name}.tsx{outputDir}/tokens-snapshot.json with design token summaryToken budget: Each figma_to_react call costs ~2-5k tokens. Cap prevents runaway costs.
components = []
for url in figmaUrls:
listing = figma_list_components(url)
nodes = listing.components.slice(0, maxComponents)
for node in nodes:
result = figma_to_react(node.id)
safeName = node.name.replace(/[^a-zA-Z0-9_-]/g, '-').slice(0, 64) // SEC-002: path sanitization
Write("{outputDir}/extractions/{safeName}.tsx", result.code)
components.push({ name: node.name, safeName, code: result.code, nodeId: node.id, url })
Write("{outputDir}/tokens-snapshot.json", extractDesignTokens(components))
See pipeline-phases.md for extraction error handling.
Conditional on designContext.builder !== null (a UI builder MCP is available) AND
designContext.synthesis_strategy !== "tailwind" (detection found a matchable framework).
if designContext.builder === null OR designContext.synthesis_strategy === "tailwind":
// No builder or pure tailwind strategy — skip matching, Phase 3 uses raw output
SKIP to Phase 3
matchResults = []
consecutiveFailures = 0
timeout = talisman?.design_sync?.reference_timeout_ms ?? 15000
threshold = talisman?.design_sync?.library_match_threshold ?? 0.5
for component in components:
if consecutiveFailures >= 3:
BREAK // Circuit breaker
try:
matches = builderProfile.search(component.name, { timeout })
bestMatch = matches.filter(m => m.score >= threshold)[0]
if bestMatch:
matchResults.push({ component: component.name, match: bestMatch })
consecutiveFailures = 0
else:
matchResults.push({ component: component.name, match: null })
catch:
consecutiveFailures++
Write("{outputDir}/match-report.json", matchResults)
See pipeline-phases.md for circuit breaker details and text-mode matching.
Combines Figma reference code with library matches to produce prototype components.
Uses designContext.synthesis_strategy to determine code generation approach:
"library" → use library adapter for correct props, imports, icons via Semantic IR"hybrid" → Tailwind CSS with library naming conventions"tailwind" → raw Tailwind CSS from figma-to-react outputAdapter-based code generation pipeline (DEPTH-001):
selectAdapter(designContext) → returns UNTITLEDUI_ADAPTER, SHADCN_ADAPTER, or TAILWIND_ADAPTER based on synthesis_strategy and detected library. Falls back to Tailwind if no adapter matches (DEPTH-003).extractSemanticIR(figmaRef) → produces SemanticComponent[] with type, intent, size, state, icons. Covers all 15 component types.generateComponentCode(irComp, adapter) → maps each SemanticComponent through the adapter's type mapping table for correct props, variants, sizes, icons, and state props.prototype.tsx.adapter = selectAdapter(designContext)
for component in components:
figmaRef = Read("{outputDir}/prototypes/{component.name}/figma-reference.tsx")
irComponents = extractSemanticIR(figmaRef)
codeFragments = irComponents.map(ir => generateComponentCode(ir, adapter))
prototype = composePrototype(codeFragments, extractLayoutIntent(figmaRef))
Write("{outputDir}/prototypes/{component.name}/prototype.tsx", prototype)
if NOT flags.noStorybook:
storyCode = generateStory(component, prototype, stackInfo)
Write("{outputDir}/prototypes/{component.name}/prototype.stories.tsx", storyCode)
When designContext.domain is present with confidence >= 0.70 and domain is not "general",
inject domain-specific hints into the synthesis prompt as the lowest-trust context layer:
IF designContext.domain AND designContext.domain.confidence >= 0.70
AND designContext.domain.inferred !== "general":
domainGuide = Read("frontend-design-patterns/references/domain-design-guide.md",
section=designContext.domain.inferred)
synthesisContext += "\n\n## Domain Context: {designContext.domain.inferred}\n{domainGuide}"
Domain hints inform UX priorities and anti-patterns but never override Figma specs, VSM tokens, or library adapter output.
See prototype-conventions.md for synthesis rules, library-specific import patterns, and adapter-aware conventions.
See library-adapters.md for adapter registry and selectAdapter().
See semantic-ir.md for SemanticComponent interface and extractSemanticIR().
See domain-design-guide.md for per-domain design recommendations.
Conditional: only runs when >= 2 components were extracted. Analyzes relationships between components to produce a navigation and data flow map.
if components.length >= 2:
flowMap = analyzeComponentRelationships(components, matchResults)
Write("{outputDir}/flow-map.md", flowMap)
Conditional: runs when >= 1 prototype was generated. Performs structural self-review:
storybook.enabled and Storybook is running. See pipeline-phases.md for details.prototypes = Glob("{outputDir}/prototypes/*/prototype.tsx")
if prototypes.length === 0:
SKIP to Phase 5
issues = []
for proto in prototypes:
content = Read(proto)
issues.push(...verifyPrototype(content))
Write("{outputDir}/verify-report.md", formatVerifyReport(issues))
Bootstraps an ephemeral Storybook environment at tmp/storybook/, copies prototypes into it, and launches the dev server. The tmp/storybook/ directory is session-scoped — cleaned by /rune:rest.
Gate: --no-storybook is NOT set AND prototypes were generated.
Uses scripts/storybook/bootstrap.sh which handles: scaffold (once) → install deps (once) → copy prototypes → detect full-page composition → return JSON { storybook_dir, full_page_component, ready }. After bootstrap, kills existing Storybook on port 6006, launches fresh, and opens the full-page composition (or first component) in browser.
See pipeline-phases.md for the full Phase 4.5 implementation code and bootstrap script details.
Aggregate all reports and present to user with actionable next steps.
summary = {
components_extracted: components.length,
library_matches: matchResults.filter(m => m.match).length,
prototypes_generated: Glob("{outputDir}/prototypes/*/prototype.tsx").length,
stories_generated: Glob("{outputDir}/prototypes/*/*.stories.tsx").length,
issues_found: issues.length,
output_dir: outputDir,
storybook_launched: summary.storybook_launched || false,
storybook_url: summary.storybook_url || null,
full_page_component: findFullPageComponent(prototypeFiles) || null
}
Write("{outputDir}/summary.json", summary)
// Persist design learnings to echoes
const echoLib = `${RUNE_PLUGIN_ROOT}/scripts/lib/echo-append.sh`
if (summary.prototypes_generated > 0) {
Bash(`source "${echoLib}" && rune_echo_append \
--role designer --layer observations \
--source "rune:design-prototype" \
--title "Prototype patterns: ${summary.components_extracted} components" \
--content "Generated ${summary.prototypes_generated} prototypes with ${summary.library_matches} library matches" \
--confidence MEDIUM \
--domain design \
--tags "design,prototype,${designContext.synthesis_strategy}"`)
}
// Present full-page component prominently
if (summary.full_page_component) {
present(`Full screen preview: ${summary.full_page_component}`)
present(`Open: ${summary.storybook_url}`)
}
AskUserQuestion(formatSummary(summary) + "\n\nNext steps:\n" +
(summary.storybook_launched
? "1. ✅ Storybook running — full screen preview opened in browser\n"
: "1. Run Storybook to preview: cd tmp/storybook && npx storybook dev\n") +
"2. Run /rune:design-sync <url> for full implementation pipeline\n" +
"3. Regenerate with different options\n\n" +
"Choose an option or provide feedback:")
See report-format.md for summary formatting.
Per-component: tmp/design-prototype/{timestamp}/{component-name}/ with extraction.tsx, prototype.tsx, prototype.stories.tsx, match.json. Per-run: tokens-snapshot.json, match-report.json, flow-map.md, verify-report.md, summary.json. Storybook runtime at tmp/storybook/ (session-scoped, cleaned by /rune:rest).
See report-format.md § Output Directory Structure for the full tree.
When >= 3 components AND --no-team is NOT set, uses Agent Teams for parallel extraction/synthesis with up to 5 proto-worker teammates. Trust hierarchy: Figma > tokens > library match > stack conventions > Storybook patterns > defaults.
See team-architecture.md for team setup, cleanup, fallback array, and trust hierarchy details.
| Error | Response | Recovery |
|---|---|---|
design_sync.enabled is false | INTERACTIVE: AskUserQuestion with setup instructions | Enable in talisman.yml |
No Figma URL and no --describe | INTERACTIVE: AskUserQuestion requesting input | Provide URL or description |
| Figma MCP not available | INTERACTIVE: AskUserQuestion with MCP setup options | Configure MCP in .mcp.json |
| figma_to_react fails for a component | WARN: skip component, continue pipeline | Retry with different node-id |
| Builder search timeout | Circuit breaker after 3 failures, skip remaining | Prototypes use raw Figma output |
| All extractions fail | INTERACTIVE: AskUserQuestion reporting failure | Check Figma URL validity |
| Storybook generation fails | WARN: write prototype without story | Manual story creation |
# talisman.yml — under design_sync section
design_sync:
enabled: false # Master toggle (shared with design-sync)
prototype_generation: true # Enable prototype output (default: true)
storybook_preview: true # Generate Storybook stories (default: true)
max_reference_components: 5 # Max components to extract per URL
reference_timeout_ms: 15000 # Per-component figma_to_react timeout in ms (Phase 1 extraction)
library_timeout_ms: 10000 # Per-component UntitledUI search timeout in ms
library_match_threshold: 0.5 # Min score to accept a library match
# talisman.yml — under design_prototype section (Phase 0 detection)
design_prototype:
detection_timeout_ms: 5000 # Max time for 3-layer detection pipeline (ARCH-004)
cache_enabled: true # Enable detection result caching (BACK-008)