Agent Skillskinncj/Heimdall › wireframe

wireframe

GitHub

根据用户故事文件生成低保真线框图,支持ASCII、SVG或HTML格式。自动适配Web和TUI目标,确保输出完整并需人工审批后方可进入下游设计阶段。

.opencode/skills/wireframe/SKILL.md kinncj/Heimdall

Trigger Scenarios

需要为UI故事创建线框图 从用户故事文件生成低保真原型

Install

npx skills add kinncj/Heimdall --skill wireframe -g -y
More Options

Non-standard path

npx skills add https://github.com/kinncj/Heimdall/tree/main/.opencode/skills/wireframe -g -y

Use without installing

npx skills use kinncj/Heimdall@wireframe

指定 Agent (Claude Code)

npx skills add kinncj/Heimdall --skill wireframe -a claude-code -g -y

安装 repo 全部 skill

npx skills add kinncj/Heimdall --all -g -y

预览 repo 内 skill

npx skills add kinncj/Heimdall --list

SKILL.md

Frontmatter
{
    "name": "wireframe",
    "description": "Generate low-fidelity wireframes (ASCII, SVG, or HTML) from user story files. Use when creating wireframes for UI stories."
}

SKILL: wireframe

Purpose

Generate low-fidelity wireframes from user story files. Output is deterministic — given the same story and layout hints, the same wireframe structure is produced. Three output formats: ASCII (default), SVG, HTML. Human approval is required before the wireframe feeds downstream mockup work.

Inputs

Field Source Example
story_file path to story markdown docs/stories/auth-reset-0001.md
ui_components derived from story Gherkin form, button, error message
stack project.config.yaml react-mantine

Outputs

The required files depend on design.target in project.config.yaml (default web) — see Target awareness:

File Location Targets
<story-id>.wireframe.md docs/design/wireframes/ web + tui — ASCII layout + approval metadata
<story-id>.wireframe.excalidraw docs/design/wireframes/ web + tui — editable Excalidraw diagram
<story-id>.wireframe.html docs/design/wireframes/ web only — browser-previewable static wireframe

A wireframe stage that produces fewer than the files required for the active target is incomplete. Do not PAUSE or mark DONE without them.

Target awareness

design.target decides which files are required:

  • web<id>.wireframe.md (ASCII) + .html (preview) + .excalidraw.
  • tui<id>.wireframe.md (ASCII/box-drawing) + .excalidraw. No .html.

Use box-drawing primitives for tui layouts, label panes/overlays/status bar, and include the keybinding legend and focus order inline in the .md. The Excalidraw generator below applies to both targets; the HTML generator is web only.

ASCII Wireframe Primitives

Use these consistently across all wireframes:

┌─────────────────────────────────┐   ← container / card
│  [Label]  [Input Field      ]   │   ← label + text input
│  [Button: Primary Action    ]   │   ← primary button
│  [Button: Secondary]            │   ← secondary button
│  ○ Option A  ○ Option B         │   ← radio group
│  ☐ Checkbox label               │   ← checkbox
│  ▼ Dropdown / Select            │   ← select / combobox
│  ──────────────────────         │   ← divider
│  ⚠ Error message text           │   ← validation error
│  ✓ Success confirmation         │   ← success state
└─────────────────────────────────┘

[Nav: Logo | Item 1 | Item 2 | CTA]  ← navigation bar
[ Sidebar  ][     Main Content    ]  ← two-column layout
[  Col 1  ][  Col 2  ][  Col 3  ]   ← three-column grid
[         Full-width Banner         ]← hero / header band

ASCII Wireframe — Example (Password Reset)

┌──────────────────────────────────────┐
│            Reset Password            │
│                                      │
│  Email                               │
│  [                              ]    │
│                                      │
│  ⚠ No account found for this email  │  ← error state
│                                      │
│  [Button: Send Reset Link       ]    │
│                                      │
│  ← Back to Login                     │
└──────────────────────────────────────┘

Generate Wireframe Files

For each story, produce all three output files. Run these steps:

STORY_FILE="docs/stories/auth-reset-password-20250416143000-0001.md"
STORY_ID=$(python3 -c "
import re
m = re.search(r'^id:\s*[\"\'](.*?)[\"\']', open('$STORY_FILE').read(), re.MULTILINE)
print(m.group(1) if m else 'unknown')
")
mkdir -p docs/design/wireframes

Step 1 — Write ${STORY_ID}.wireframe.md (ASCII layout + approval metadata):

---
story_id: "{story_id}"
story_file: "{story_file}"
status: draft          # draft | approved | rejected
approved_by: null
approved_at: null
---

## Wireframe: {story title}

### Default state
{ASCII wireframe}

### Error state
{ASCII wireframe — validation error}

### Success state
{ASCII wireframe — confirmation}

### Interaction Notes

- Tab order: {list of focusable elements in tab sequence}
- Primary action: {describe}
- Error handling: {describe visible error states}

### Approval

- [ ] Approved by product owner
- [ ] Approved by UX lead (if applicable)

Step 2 — Write ${STORY_ID}.wireframe.html (see HTML template below)

Step 3 — Write ${STORY_ID}.wireframe.excalidraw (see Excalidraw template below)

After writing the files required for the active target, verify they exist (web: md, html, excalidraw; tui: md, excalidraw):

ls docs/design/wireframes/${STORY_ID}.wireframe.md
ls docs/design/wireframes/${STORY_ID}.wireframe.excalidraw
# web target also:
ls docs/design/wireframes/${STORY_ID}.wireframe.html 2>/dev/null || true

If any required file is missing, produce it before continuing.

HTML Wireframe (web target only)

For web targets, always generate the HTML wireframe. Skip this section entirely for tui. Use multiple <section> blocks for multi-state wireframes (default, loading, error, success, empty).

cat > "docs/design/wireframes/${STORY_ID}.wireframe.html" <<'HTMLEOF'
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Wireframe: {story title}</title>
  <style>
    * { box-sizing: border-box; font-family: monospace; }
    body { background: #e9ecef; padding: 2rem; }
    h1 { font-size: 1rem; color: #495057; margin-bottom: 1.5rem; }
    .states { display: flex; flex-wrap: wrap; gap: 1.5rem; }
    .state { background: #f8f9fa; border: 1px solid #adb5bd; border-radius: 4px; padding: 0; min-width: 360px; }
    .state-label { background: #343a40; color: #fff; font-size: .75rem; padding: .25rem .75rem; border-radius: 4px 4px 0 0; }
    .frame { padding: 1.5rem; }
    .screen-title { font-weight: bold; font-size: 1.1rem; margin-bottom: 1rem; border-bottom: 1px solid #dee2e6; padding-bottom: .5rem; }
    label { display: block; font-size: .8rem; color: #495057; margin-bottom: .2rem; margin-top: .75rem; }
    .input { border: 1px solid #868e96; padding: .4rem .6rem; width: 100%; background: #fff; }
    .btn { border: none; padding: .5rem 1rem; cursor: default; margin-top: .75rem; width: 100%; font-weight: bold; }
    .btn-primary { background: #343a40; color: #fff; }
    .btn-secondary { background: transparent; border: 1px solid #343a40; color: #343a40; }
    .error { color: #c0392b; font-size: .8rem; margin-top: .25rem; }
    .success { color: #2d6a4f; font-size: .8rem; margin-top: .25rem; }
    .link { color: #1971c2; font-size: .85rem; margin-top: .75rem; display: block; }
    .nav { display: flex; gap: 1rem; background: #343a40; color: #fff; padding: .6rem 1rem; font-size: .85rem; margin-bottom: .75rem; }
    .badge { background: #868e96; color: #fff; font-size: .7rem; padding: .1rem .4rem; border-radius: 3px; }
    .divider { border: none; border-top: 1px solid #dee2e6; margin: .75rem 0; }
    .tab-order { font-size: .7rem; color: #868e96; margin-top: 1.5rem; border-top: 1px dashed #dee2e6; padding-top: .5rem; }
  </style>
</head>
<body>
  <h1>Wireframe: {story title} — {story_id}</h1>
  <div class="states">

    <div class="state">
      <div class="state-label">Default state</div>
      <div class="frame">
        <div class="screen-title">{Screen Title}</div>
        <!-- Add form fields, buttons, content blocks here -->
        <label>Field label</label>
        <input class="input" type="text" placeholder="placeholder" disabled />
        <div class="btn btn-primary">Primary Action</div>
        <a class="link" href="#">Secondary link</a>
        <div class="tab-order">Tab order: Field → Primary Action → Secondary link</div>
      </div>
    </div>

    <div class="state">
      <div class="state-label">Error state</div>
      <div class="frame">
        <div class="screen-title">{Screen Title}</div>
        <label>Field label</label>
        <input class="input" type="text" placeholder="placeholder" disabled style="border-color:#c0392b" />
        <div class="error">⚠ Error message describing the problem</div>
        <div class="btn btn-primary">Primary Action</div>
        <div class="tab-order">Tab order: Field → Primary Action</div>
      </div>
    </div>

    <div class="state">
      <div class="state-label">Success state</div>
      <div class="frame">
        <div class="screen-title">{Screen Title}</div>
        <div class="success">✓ Success confirmation message</div>
        <div class="btn btn-secondary">Back / Next step</div>
      </div>
    </div>

  </div>
</body>
</html>
HTMLEOF

Extend with real field names, content, and states from the story Gherkin. One <div class="state"> block per Gherkin scenario.

Excalidraw Wireframe (always required)

Always generate the Excalidraw file — every story, every run. Excalidraw is the canonical editable wireframe format reviewers annotate.

Write the file as valid JSON to docs/design/wireframes/${STORY_ID}.wireframe.excalidraw. Each UI element is one entry in the elements array. Use the element templates below, copy and adapt:

python3 - <<'PYEOF'
import json, pathlib, os

story_id = os.environ.get("STORY_ID", "unknown")
out = pathlib.Path(f"docs/design/wireframes/{story_id}.wireframe.excalidraw")
out.parent.mkdir(parents=True, exist_ok=True)

# ── Element helpers ──────────────────────────────────────────────────────────
def rect(id, x, y, w, h, label="", bg="transparent", stroke="#343a40", bold=False):
    els = [{
        "id": id, "type": "rectangle", "x": x, "y": y, "width": w, "height": h,
        "angle": 0, "strokeColor": stroke, "backgroundColor": bg,
        "fillStyle": "solid", "strokeWidth": 2, "strokeStyle": "solid",
        "roughness": 1, "opacity": 100, "groupIds": [], "roundness": {"type": 3},
        "version": 1, "versionNonce": 1, "isDeleted": False,
        "boundElements": None, "updated": 1, "link": None, "locked": False,
    }]
    if label:
        els.append(text(id + "_lbl", x + w/2, y + h/2, label, bold=bold, anchor="center"))
    return els

def text(id, x, y, content, bold=False, anchor="left", color="#343a40"):
    return {
        "id": id, "type": "text", "x": x, "y": y,
        "width": len(content) * 8, "height": 20,
        "angle": 0, "strokeColor": color, "backgroundColor": "transparent",
        "fillStyle": "solid", "strokeWidth": 1, "strokeStyle": "solid",
        "roughness": 1, "opacity": 100, "groupIds": [], "roundness": None,
        "version": 1, "versionNonce": 1, "isDeleted": False,
        "boundElements": None, "updated": 1, "link": None, "locked": False,
        "text": content, "fontSize": 16,
        "fontFamily": 3,  # monospace
        "textAlign": anchor, "verticalAlign": "middle",
        "baseline": 14, "containerId": None, "originalText": content,
        "lineHeight": 1.25,
        "fontWeight": "bold" if bold else "normal",
    }

def input_field(id, x, y, w, label_text):
    return (
        [text(id + "_lbl", x, y - 18, label_text)] +
        rect(id, x, y, w, 32, stroke="#868e96")
    )

def button_primary(id, x, y, w, label_text):
    return rect(id, x, y, w, 36, label=label_text, bg="#343a40", stroke="#343a40", bold=True)

def button_secondary(id, x, y, w, label_text):
    return rect(id, x, y, w, 36, label=label_text, bg="transparent", stroke="#343a40")

def section_label(id, x, y, content):
    return [text(id, x, y, f"[ {content} ]", color="#868e96")]

# ── Build elements ────────────────────────────────────────────────────────────
elements = []

# Outer frame
elements += rect("frame", 50, 30, 700, 600, stroke="#343a40")

# Screen title
elements.append(text("title", 70, 50, "{Screen Title}", bold=True))

# --- Default state ---
elements += section_label("s_default", 70, 90, "Default state")
elements += input_field("field1", 70, 130, 560, "Field label")
elements += button_primary("btn_primary", 70, 190, 560, "Primary Action")
elements.append(text("link1", 70, 238, "← Secondary link / back", color="#1971c2"))

# --- Error state ---
elements += section_label("s_error", 70, 280, "Error state")
elements += input_field("field1_err", 70, 320, 560, "Field label")
elements += rect("err_border", 70, 320, 560, 32, stroke="#c0392b")
elements.append(text("err_msg", 70, 360, "⚠ Error message text", color="#c0392b"))
elements += button_primary("btn_primary_err", 70, 390, 560, "Primary Action")

# --- Success state ---
elements += section_label("s_success", 70, 450, "Success state")
elements.append(text("success_msg", 70, 490, "✓ Success confirmation", color="#2d6a4f"))
elements += button_secondary("btn_back", 70, 520, 260, "Back / Next step")

doc = {
    "type": "excalidraw",
    "version": 2,
    "source": "MAPLE wireframe-architect",
    "elements": elements,
    "appState": {"viewBackgroundColor": "#f8f9fa", "gridSize": None},
    "files": {},
}
out.write_text(json.dumps(doc, indent=2) + "\n")
print(f"[wireframe] wrote {out}")
PYEOF

Adapt element positions and labels to match the actual story screens. Add more rect/text/input_field/button_primary calls per Gherkin scenario. Do not leave placeholder text ({Screen Title}) in the final file.

Approval Gate

All three files must exist and the .md must be status: approved before mockup or ui-mockup-builder proceeds:

python3 - <<'EOF'
import re, sys, pathlib
sid = open(".claude/state/maple.json").read()  # or pass STORY_ID
# Check required artifacts exist (html is web-only)
cfg = open("project.config.yaml").read() if pathlib.Path("project.config.yaml").exists() else ""
tm = re.search(r'^\s*target:\s*(\w+)', cfg, re.MULTILINE)
target = tm.group(1) if tm else "web"
exts = ["md", "excalidraw"] if target == "tui" else ["md", "html", "excalidraw"]
for ext in exts:
    p = pathlib.Path(f"docs/design/wireframes/{sid}.wireframe.{ext}")
    if not p.exists():
        print(f"BLOCKED: missing {p}")
        sys.exit(1)
# Check approval status in .md
md = pathlib.Path(f"docs/design/wireframes/{sid}.wireframe.md").read_text()
m = re.search(r'^status:\s*(\w+)', md, re.MULTILINE)
status = m.group(1) if m else 'draft'
if status != 'approved':
    print(f"BLOCKED: wireframe {sid} not approved (status={status})")
    sys.exit(1)
print("approved — all three artifacts present")
EOF

Failure Modes

Condition Action
Story has no Gherkin Generate skeleton wireframe with placeholder states. Log NO_GHERKIN — skeleton only.
docs/design/wireframes/ missing Create it.
Wireframe .md exists and is approved Do not overwrite. Log SKIP — approved wireframe exists.
Wireframe .md exists and is draft Overwrite only if story Gherkin has changed.
.html or .excalidraw missing despite .md existing Generate the missing file(s) immediately.

Logging

[wireframe] CREATE   docs/design/wireframes/auth-reset-0001.wireframe.md
[wireframe] CREATE   docs/design/wireframes/auth-reset-0001.wireframe.html
[wireframe] CREATE   docs/design/wireframes/auth-reset-0001.wireframe.excalidraw
[wireframe] SKIP     docs/design/wireframes/auth-reset-0001.wireframe.md  (approved — locked)
[wireframe] BLOCKED  auth-reset-0001  status=draft — needs approval before mockup
[wireframe] BLOCKED  auth-reset-0001  missing .html — generating now

Version History

  • f4ea31f Current 2026-07-05 10:45

Same Skill Collection

.claude/skills/a11y-audit/SKILL.md
.claude/skills/component-scaffold/SKILL.md
.claude/skills/cucumber-automation/SKILL.md
.claude/skills/design-tokens/SKILL.md
.claude/skills/docker-patterns/SKILL.md
.claude/skills/finops-review/SKILL.md
.claude/skills/gh-issues/SKILL.md
.claude/skills/gh-labels-milestones/SKILL.md
.claude/skills/gh-projects/SKILL.md
.claude/skills/gherkin-authoring/SKILL.md
.claude/skills/github-cli/SKILL.md
.claude/skills/humanizer/SKILL.md
.claude/skills/jupyter-patterns/SKILL.md
.claude/skills/karpathy-audit/SKILL.md
.claude/skills/kubernetes-patterns/SKILL.md
.claude/skills/mermaid-diagrams/SKILL.md
.claude/skills/mockup/SKILL.md
.claude/skills/pipeline-runner/SKILL.md
.claude/skills/playwright-cli/SKILL.md
.claude/skills/postgresql-patterns/SKILL.md
.claude/skills/redis-patterns/SKILL.md
.claude/skills/rfc-adr/SKILL.md
.claude/skills/rubber-duck/SKILL.md
.claude/skills/ship-safe/SKILL.md
.claude/skills/spec-kit/SKILL.md
.claude/skills/sre-review/SKILL.md
.claude/skills/story-issue-sync/SKILL.md
.claude/skills/stripe-patterns/SKILL.md
.claude/skills/supabase-patterns/SKILL.md
.claude/skills/tdd-workflow/SKILL.md
.claude/skills/terraform-patterns/SKILL.md
.claude/skills/threat-modeling/SKILL.md
.claude/skills/vercel-patterns/SKILL.md
.claude/skills/visual-identity/SKILL.md
.claude/skills/wireframe/SKILL.md
.cursor/skills/a11y-audit/SKILL.md
.cursor/skills/component-scaffold/SKILL.md
.cursor/skills/cucumber-automation/SKILL.md
.cursor/skills/design-tokens/SKILL.md
.cursor/skills/docker-patterns/SKILL.md
.cursor/skills/finops-review/SKILL.md
.cursor/skills/gh-issues/SKILL.md
.cursor/skills/gh-labels-milestones/SKILL.md
.cursor/skills/gh-projects/SKILL.md
.cursor/skills/gherkin-authoring/SKILL.md
.cursor/skills/github-cli/SKILL.md
.cursor/skills/humanizer/SKILL.md
.cursor/skills/jupyter-patterns/SKILL.md
.cursor/skills/karpathy-audit/SKILL.md
.cursor/skills/kubernetes-patterns/SKILL.md
.cursor/skills/mermaid-diagrams/SKILL.md
.cursor/skills/mockup/SKILL.md
.cursor/skills/pipeline-runner/SKILL.md
.cursor/skills/playwright-cli/SKILL.md
.cursor/skills/postgresql-patterns/SKILL.md
.cursor/skills/redis-patterns/SKILL.md
.cursor/skills/rfc-adr/SKILL.md
.cursor/skills/rubber-duck/SKILL.md
.cursor/skills/ship-safe/SKILL.md
.cursor/skills/spec-kit/SKILL.md
.cursor/skills/sre-review/SKILL.md
.cursor/skills/story-issue-sync/SKILL.md
.cursor/skills/stripe-patterns/SKILL.md
.cursor/skills/supabase-patterns/SKILL.md
.cursor/skills/tdd-workflow/SKILL.md
.cursor/skills/terraform-patterns/SKILL.md
.cursor/skills/threat-modeling/SKILL.md
.cursor/skills/vercel-patterns/SKILL.md
.cursor/skills/visual-identity/SKILL.md
.cursor/skills/wireframe/SKILL.md
.opencode/skills/a11y-audit/SKILL.md
.opencode/skills/component-scaffold/SKILL.md
.opencode/skills/cucumber-automation/SKILL.md
.opencode/skills/design-tokens/SKILL.md
.opencode/skills/docker-patterns/SKILL.md
.opencode/skills/finops-review/SKILL.md
.opencode/skills/gh-issues/SKILL.md
.opencode/skills/gh-labels-milestones/SKILL.md
.opencode/skills/gh-projects/SKILL.md
.opencode/skills/gherkin-authoring/SKILL.md
.opencode/skills/github-cli/SKILL.md
.opencode/skills/humanizer/SKILL.md
.opencode/skills/jupyter-patterns/SKILL.md
.opencode/skills/karpathy-audit/SKILL.md
.opencode/skills/kubernetes-patterns/SKILL.md
.opencode/skills/mermaid-diagrams/SKILL.md
.opencode/skills/mockup/SKILL.md
.opencode/skills/pipeline-runner/SKILL.md
.opencode/skills/playwright-cli/SKILL.md
.opencode/skills/postgresql-patterns/SKILL.md
.opencode/skills/redis-patterns/SKILL.md
.opencode/skills/rfc-adr/SKILL.md
.opencode/skills/rubber-duck/SKILL.md
.opencode/skills/ship-safe/SKILL.md
.opencode/skills/spec-kit/SKILL.md
.opencode/skills/sre-review/SKILL.md
.opencode/skills/story-issue-sync/SKILL.md
.opencode/skills/stripe-patterns/SKILL.md
.opencode/skills/supabase-patterns/SKILL.md
.opencode/skills/tdd-workflow/SKILL.md
.opencode/skills/terraform-patterns/SKILL.md
.opencode/skills/threat-modeling/SKILL.md
.opencode/skills/vercel-patterns/SKILL.md
.opencode/skills/visual-identity/SKILL.md

Metadata

Files
0
Version
f4ea31f
Hash
1bdf5e6d
Indexed
2026-07-05 10:45

Главная - Вики-сайт
Copyright © 2011-2026 iteam. Current version is 2.155.2. UTC+08:00, 2026-07-09 05:27
浙ICP备14020137号-1 $Гость$