Add a plan-agent:design skill plus the frontmatter, renderer, gallery, and drift wiring that binds a published design canvas to the plan it came from, and offer it alongside prototype at the end of the planning chain for UI plans.
A design canvas and the plan it belongs to currently have no link between them, and nothing offers either a prototype or a design at the moment a plan is finished. We will know this worked when authoring a UI plan offers a design canvas, the finished plan carries a clickable link to it, and build reads the artboards as the visual spec.
Read and implement all steps in the plan at docs/plans/add-design-phase.md — Give plan-agent a design phase, so a plan can be seen before it is built. Verify against the plan's Tests, Verification, and Acceptance Criteria before reporting done. If everything passed, mark completion in docs/plans/add-design-phase.md — tick each step's [x] marker and each criterion's - [x], set status: completed — and re-render the HTML from the spec. If any check failed, leave status: in-progress and say which.
More ways to run this plan — goal & workflow prompts, file path
Achieve this goal: Give plan-agent a design phase, so a plan can be seen before it is built. The plan at docs/plans/add-design-phase.md describes one approach — use it as reference, but optimize for the outcome. Fan out across parallel subagents where that serves the outcome. Verify against the plan's Tests, Verification, and Acceptance Criteria before reporting done. If everything passed, mark completion in docs/plans/add-design-phase.md — tick each step's [x] marker and each criterion's - [x], set status: completed — and re-render the HTML from the spec. If any check failed, leave status: in-progress and say which.
Run a workflow to implement the plan at docs/plans/add-design-phase.md — Give plan-agent a design phase, so a plan can be seen before it is built. Brief subagents with the plan file at docs/plans/add-design-phase.md. Reserve a final verification phase for the lead agent, not a subagent. Verify against the plan's Tests, Verification, and Acceptance Criteria before reporting done. If everything passed, mark completion in docs/plans/add-design-phase.md — tick each step's [x] marker and each criterion's - [x], set status: completed — and re-render the HTML from the spec. If any check failed, leave status: in-progress and say which.
add-design-phase.html
docs/plans/add-design-phase.html
docs/plans/add-design-phase.md
Context
The story behind this plan — what prompted the work and why it matters now.
Claude Code ships a built-in design skill that writes .dc.html artboards
into the working tree, seeds them with its own seed-canvas.mjs, and publishes
the result as an editable canvas Artifact. It already matches the host codebase's
tokens and components before drawing (its Step 0), and already asks whether the
user wants static mockups or a clickable prototype (its Step 1).
What it does not do is know about plans. Nothing links a canvas back to the plan
that motivated it, nothing offers it at the moment a plan is finished, andbuild never looks at it.
plan-agent already solved this exact problem once, for prototypes:skills/prototype/ derives a data model from a plan, writesdocs/prototypes/<slug>.html, writes a prototype: key back into the spec, and
a PostToolUse hook rebuilds the gallery and checks for drift. This plan mirrors
that treatment for designs.
One gap it also closes: nothing in the plan chain currently offers the
prototype either. No skill outside skills/prototype/ mentions it — the user
has to know the command exists. Step 8 gains one question that offers both.
Risks:
- Renderer copy drift. build-plan-html.mjs ships as two byte-identical
copies (scripts/ and kit/plugins/plan-agent/scripts/) with no parity test —
unlike build-index.sh, whose three copies are guarded bytests/plugins/test-build-index-parity.mjs. Editing one and not the other
half-ships the feature. Mitigation: step 4 edits both, and the objective test
asserts they hash identically.
- Upstream contract churn. The built-in design skill owns the .dc.html
format, the seed-canvas.mjs helper, the contract: "0.1.31" pin, and the
capability roster. All of that changes without notice. Mitigation: our skill
delegates to it via Skill(design) and reproduces none of it.
- Hook budget. dispatch.py shares one 55s deadline across every child. Two
new children join it. Mitigation: check-design-drift.py does filename and
heading comparison only — no network, no parse of the published canvas.
Decisions
Choices already settled — read these before re-opening any of them.
- Scope is a full skill mirroring
prototype, not a record-only frontmatter key. - Our skill derives artboards and links them back; the built-in
designskill owns all authoring and publishing. We never reimplement the.dc.htmlformat, the seeding helper, the contract pin, or the capability roster. - Activation is a third batched question in the existing
implementation-planStep 8 menu, not two new options — both existing option lists already sit at the 4-optionAskUserQuestioncap. - The question fires only on UI plans, reusing the
ui_signals_presentrule already defined atskills/review-plan/SKILL.md:78rather than inventing a second definition of "UI". - Drift means artboard names versus the plan's user-facing steps. It does not compare local artboards against the published canvas: people editing the canvas in the GUI is the feature working, and a check that fires on that is noise.
- Artboard derivation is one per user-facing step, uncapped. A step with no user-facing surface — a version bump, a test file, a README edit — produces no artboard. "User-facing" is load-bearing in two places: the derivation rule and the drift check must apply the same filter, or every housekeeping step reads as permanent drift.
- Pointed at a plan with no UI signals, the skill warns in one line and proceeds. It does not refuse: architecture diagrams and flow sketches are legitimate uses.
- End-to-end verification publishes a real canvas, against a throwaway plan, so a test canvas never lands in the real plan history.
- The drift hook ships in v1 rather than deferring, so the designs and prototypes galleries behave the same way from the start.
- The plan points at its design with two keys —
design:(the artifact URL, which renders the header link) anddesign-dir:(the local artboard directory, whichbuildreads). Two keys survive a plan rename; a slug-derived directory would not.
Files that change
Every file this plan touches, and what happens to each one.
`docs/plans/add-design-phase.md`new this spec- `tests/plugins/
test-design-plan-link.mjs`new objective testtest-build-designs-index.sh`new gallery generator testtest-design-drift.sh`new drift hook test
`tests/fixtures/plan-agent/sample-design/`new artboard fixture pair`scripts/build-plan-html.mjs`modifieddesign:/design-dir:keys`kit/plugins/plan-agent/scripts/build-plan-html.mjs`modified identical copy- `kit/plugins/plan-agent/hooks/
build-designs-index.sh`new designs gallerycheck-design-drift.py`new drift checkdispatch.py`modifieddocs/designs/gate
`kit/plugins/plan-agent/skills/design/SKILL.md`new the skill`kit/plugins/plan-agent/skills/implementation-plan/SKILL.md`modified Step 8 question`kit/plugins/plan-agent/skills/build/SKILL.md`modified read artboards as visual spec`kit/plugins/plan-agent/README.md`modified skill docs`README.md`modified regenerated Plugin Reference Table`.claude-plugin/marketplace.json`modified plan-agent 9.4.5 to 9.5.0
Steps
The step-by-step work, in order — each step says what to do, why it matters, and how to check it worked.
RED
tests/plugins/test-design-plan-link.mjs, modelled on tests/plugins/test-prototype-plan-link.mjs. It asserts four things: a spec carrying design: and design-dir: renders a plan-design meta tag plus a header anchor whose accessible text is non-empty and whose href is the artifact URL verbatim; a spec carrying neither renders neither; a spec carrying design: javascript:alert(1) renders neither tag nor anchor; and the two build-plan-html.mjs copies hash identically.
node tests/plugins/test-design-plan-link.mjs exits non-zero on the missing plan-design meta tag, not on a module-resolution error — paste the failing assertion.tests/plugins/test-build-designs-index.sh, modelled on tests/plugins/test-build-prototypes-index.sh. It asserts the generator writes docs/designs/index.html with one escaped card per canvas directory, skips its own generated index when re-run, and exits 0 on a directory holding no artboards.
bash tests/plugins/test-build-designs-index.sh exits non-zero reporting build-designs-index.sh not found.tests/plugins/test-design-drift.sh plus the tests/fixtures/plan-agent/sample-design/ fixture — one plan spec with three user-facing steps plus one housekeeping step, and a matching three-artboard directory, and one diverged pair where the spec gained a fourth user-facing step with no artboard. It asserts check-design-drift.py is silent on the matched pair — including its housekeeping step, which must not be reported — and names the uncovered step on the diverged one.
bash tests/plugins/test-design-drift.sh exits non-zero reporting check-design-drift.py not found.GREEN
design: and design-dir: handling to both copies of build-plan-html.mjs (scripts/ and kit/plugins/plan-agent/scripts/), modelled on the existing issue: block near line 313 — http(s) only, drop anything else for both the tag and the link — emitting a plan-design meta tag and a "View design" header action row link. Do not model it on the prototype: block: that relativizes a repo path, and a canvas lives at a URL.
issue: block is the one that already solves URL-valued keys safely.node tests/plugins/test-design-plan-link.mjs passes all four assertions, including the identical-hash check.kit/plugins/plan-agent/hooks/build-designs-index.sh, forked from build-prototypes-index.sh and retargeted to docs/designs/. Keep the same two run modes (hook payload on stdin, or a project root argument) and the same exit 0 guarantee.
bash tests/plugins/test-build-designs-index.sh passes.kit/plugins/plan-agent/hooks/check-design-drift.py. It reads the plan's design-dir:, lists *.dc.html artboard names in that directory, extracts the plan's user-facing step headings — applying the same filter step 8's derivation uses, so a version bump or a test-file step is never counted — and reports any user-facing step with no artboard covering it. No network calls, no read of the published canvas.
bash tests/plugins/test-design-drift.sh passes both the matched and diverged fixtures.docs/designs/ gate to kit/plugins/plan-agent/hooks/dispatch.py — a _DESIGNS_MARKER constant, an is_design test alongside is_prototype, and a fan-out to the two new children sharing the existing deadline. Preserve the early sys.exit(0) for unrelated writes.
dispatch.py is the plugin's only registered hook; a child not wired here never runs.python3 -c a synthetic payload for a docs/designs/x/Main.dc.html write through dispatch.py and confirm both children ran; repeat with an unrelated path and confirm no child process spawned.kit/plugins/plan-agent/skills/design/SKILL.md, structurally mirroring skills/prototype/SKILL.md: Step 0 exit plan mode; Step 1 resolve the input (plan path, raw idea, image, or Figma URL — the same input contract prototype already documents), warning in one line and proceeding when the resolved plan carries no UI signals; Step 2 derive one artboard per user-facing step, uncapped — a step with no user-facing surface produces no artboard; Step 3 echo the artboard list back for confirmation; Step 4 delegate authoring and publishing to the built-in skill via Skill(design), writing working files under docs/designs/<plan-slug>/; Step 5 write design: and design-dir: into the spec frontmatter and re-render; Step 6 index and report.
bash tests/plugins/test-build-skill.sh and bash tests/plugins/test-description-budget.sh both pass.implementation-plan/SKILL.md Step 8: "Want to see it before building?" with options Prototype, Design canvas, and No. Gate it on the ui_signals_present rule quoted from skills/review-plan/SKILL.md:78. It is a third question in the same AskUserQuestion call, never two more options — both existing option lists are already at the 4-option cap.
bash tests/plugins/test-exitplanmode-guard.sh still passes, and a grep of the edited Step 8 shows three questions in one call with no option list longer than four.skills/build/SKILL.md Step 2 to read the artboards under a spec's design-dir: as the visual spec before implementing, when the key is present.
build/SKILL.md for design-dir and confirm the instruction sits inside Step 2, before the implementation loop.VERIFY
bash tests/run-all.sh.
plan-agent to 9.5.0 in .claude-plugin/marketplace.json, regenerate the root Plugin Reference Table with node scripts/build-readme-table.mjs, and hand-write the design section in kit/plugins/plan-agent/README.md alongside the existing prototype section.
git fetch origin && BASE_REF=main node scripts/check-plugin-versions.mjs exits 0, and git diff README.md shows only generator-shaped changes.Design canvas branch at Step 8, and confirm the canvas publishes, both frontmatter keys are written, the re-rendered plan carries the header link, and the designs gallery lists the new canvas. Then load the rendered plan in the browser and assert the anchor's resolved href and its computed text via mcp__claude-in-chrome__javascript_tool, reporting both measured values.
href and text content. A screenshot alone is not evidence.Tests
The tests that prove the change does what it promises.
tests/plugins/test-design-plan-link.mjs; Type: smoke; Asserts: a spec carrying design: and design-dir: renders the plan-design meta tag and a header anchor with non-empty text and the artifact URL as href; a spec without them renders neither; a javascript: value renders neither; both renderer copies hash identically; Run: node tests/plugins/test-design-plan-link.mjstests/plugins/test-build-designs-index.sh; Targets: hooks/build-designs-index.sh; Key cases: one card per canvas directory with escaped titles, self-written index skipped on re-run, exit 0 on a directory with no artboards.tests/plugins/test-design-drift.sh; Targets: hooks/check-design-drift.py; Key cases: silent on a matched artboard/step fixture, silent on a housekeeping step that has no artboard by design, names the uncovered user-facing step on a diverged one.tests/run-all.sh; Key cases: the three new tests are auto-discovered and pass alongside the existing 77.Definition of done
The plan counts as done when every statement below is true — check each one off as you verify it.
Final check
One last pass to confirm the whole change works end to end.
Author a UI plan through /plan-agent:implementation-plan. At Step 8, confirm
the third question appears and take the Design canvas branch. Confirm the
built-in design skill runs, artboards land under docs/designs/<slug>/, and a
canvas Artifact URL comes back.
Reopen the re-rendered plan HTML. The header carries a "View design" link
pointing at that URL, and the page's plan-design meta tag holds the same value.
Open docs/designs/index.html and confirm the canvas appears as a card.
Add a fourth user-facing step to the plan spec and re-render. The drift check
reports that step as uncovered. Open the canvas, move an element, and save. The
drift check stays silent — that edit is the canvas working as intended.
Finally run bash tests/run-all.sh andgit fetch origin && BASE_REF=main node scripts/check-plugin-versions.mjs. Both
exit clean.
Wrapping up
Three gates that must all pass before this plan is marked completed.
Completion Report
No items to report — all requirements met.