算法艺术创作
algorithmic-art
通过代码和可调参数创作生成艺术与交互式视觉效果。
skeleton-extractor【设计与创意】Use when transforming an HTML mockup into a draft design-skeleton.v1 YAML. Walks the DOM via puppeteer-core at 3 breakpoints, extracts bboxes + computed styles (resolved back to declared tokens) + wired interaction handlers, and emits one draft ready for visual-architect review. Runs the Node subprocess under trusted_runner discipline (CB3-compliant:…
独立 GitHub 快照 · 自动分类 · 以原作者文档为准
Transforms an HTML mockup into a pre-freeze draft design-skeleton.v1 YAML. One skill, one operation (extract), one subprocess, one stdout JSON blob.
Called by visual-architect at design-phase freeze (see design doc §2.5), after the user has approved an HTML mockup. The draft it emits is reviewed by the user, amended (binds_to URIs, bbox confirmations, unresolved-token decisions), then HMAC-signed and written to .design-ledger/skeletons/<screen>.yaml. Skeleton-extractor does NOT freeze — that is visual-architect's job.
visual-arbiter's job (pure-Python verdict on stdout, CB4).design-drift-arbiter's job.A single op.
extractpython3 skills/skeleton-extractor/scripts/extract.py \
--mockup /abs/path/to/mockup.html \
--out /abs/path/out.draft.yaml \
[--breakpoints 420,700,1280] \
[--tokens-path /abs/path/to/index.yaml]
Arguments:
| Flag | Required | Default | Meaning |
|---|---|---|---|
--mockup | yes | — | Absolute path to the HTML mockup (file read via file:// URL) |
--out | yes | — | Where the draft YAML is written (trusted_runner.atomic_write_bytes) |
--breakpoints | no | 420,700,1280 | Comma-list of viewport widths; one extraction pass per width |
--tokens-path | no | none | Optional path to index.yaml with declared tokens: block, used for getComputedStyle back-resolution |
--timeout | no | 120 | Node subprocess timeout seconds |
Draft YAML conforming to design-skeleton.v1 (§2.3):
schema: design-skeleton.v1
draft: true
generated_by: skeleton-extractor@1.0.0
generated_at: "2026-04-24T00:00:00Z"
breakpoints: [420, 700, 1280]
fonts_loaded: true
fonts_ready_max_ms: 412
elements:
- id: grid_card_nth_of_type_1
selector: "#grid .card:nth-of-type(1)"
role: article
kind: element
bbox:
mobile: {x:16, y:96, w:388, h:260}
tablet: {x:24, y:116, w:320, h:240}
desktop: {x:48, y:136, w:384, h:220}
tokens_used:
background_color: "token://color.paper"
border_color: "token://color.ink"
tokens_raw_per_bp: { ... } # preserved for the arbiter's audit trail
interactions:
- {event: click, binds_to: null} # user fills during visual-architect freeze
shadow_dom_opaque: false
unresolved_tokens_report:
- {selector: ".banner", field: color, computed: "#ac3b3b", breakpoint: desktop}
concerns: []
scripts/extract.py) parses args, loads declared tokens, checks that /bin/google-chrome and the Node binary exist.node skills/_meta/skeleton_extractor.mjs with a sanitized env (mirrors trusted_runner._run_pytest), a 120s timeout, and a single JSON stdin payload {mockupHtml, breakpoints, tokens}._meta/skeleton_extractor.mjs, ~300 LOC) launches puppeteer-core pointed at /bin/google-chrome. For each breakpoint: setViewport before page.goto(file://mockup, {waitUntil: "networkidle0"}), then await document.fonts.ready + 300ms settle (font-race guard, §2.10). Walks visible elements with role/data-*/event-handler, emits per-element {selector, role, bbox, tokens_raw, interactions, shadow_dom_opaque}.tokens_raw back to declared tokens (§2.4 step 4). var(--accent-sun) chains resolve through :root. First exact match wins; unresolved values are surfaced in unresolved_tokens_report — never auto-added to index.yaml (D2 strict).bbox: {mobile, tablet, desktop} map.trusted_runner.atomic_write_bytes. No partial writes, no stream output, no ledger writes from inside the subprocess.The subprocess is CB3-compliant: trusted_runner (invoked from the Python wrapper) owns subprocess execution and captures stdout; the skill/subprocess only generates a draft. The final persisted file goes through atomic_write_bytes (same helper bob uses for bundles and claims).
| Edge case | Handling |
|---|---|
| Font-loading race | fonts.ready + 300ms settle; >2s = warning concern; >5s = blocker concern + fonts_loaded: false |
| Viewport-unit CSS (vw/vh) | setViewport always called BEFORE goto() — deterministic per breakpoint |
| Pseudo-elements | Walked by DOM iteration; current version records host element tokens only; ::before/::after explicit slots are v2 |
| Shadow DOM | Open roots recursed; closed roots → shadow_dom_opaque: true marker on host (arbiter treats as black box) |
| Responsive accident vs intent | bbox deltas between neighboring breakpoints are recorded but NOT auto-flagged; visual-architect reviews |
| Animations / transitions | Static after-settle state only; must_satisfy.known_deferred: [animations] is visual-architect's concern |
| Condition | Exit | Observation emitted (category, fingerprint) |
|---|---|---|
/bin/google-chrome missing | 3 | external_tool_fail, chrome_absent |
| node binary missing | 2 | external_tool_fail, node_absent |
| Subprocess non-zero exit | 2 | external_tool_fail, exit-<N> |
| Subprocess exceeds 120s | 2 | external_tool_slow, timeout-120s |
| Malformed stdout JSON | 2 | external_tool_fail, malformed_output |
All observations are fail-open: claude_observe import errors, runtime errors inside the observation writer, or env-adoption gaps never block the caller. The Python exception still propagates so bob/visual-architect sees the authoritative failure.
waitUntil: "networkidle0" is for same-origin static assets only./path/to/project/docs/plans/2026-04-23-ecosystem-keystone-design.md (§2.3, §2.4, §2.10)progress/contract-map.yaml skeleton-extractor component (TS-SE-01..04)/path/to/projects/test_flow/test-driver.mjs (puppeteer-core invocation pattern)~/.claude/skills/_meta/skeleton_extractor.mjs~/.claude/skills/skeleton-extractor/tests/