Visual Doc
Turn a spec/plan into a single self-contained, hand-drawn-styled HTML document by authoring
a typed block array and rendering it. Unlike the recap (which is automatic), you compose the
blocks — so ground every reference in the real repo.
Tool location (resolved through the installer's ~/.claude/visual-skills symlink — re-run npm run skills:install if the repo moves):
VISUAL_SKILLS_DIR="${CLAUDE_CONFIG_DIR:-$HOME/.claude}/visual-skills"
Required language guide. Before writing user-facing text, read
$VISUAL_SKILLS_DIR/skills/shared/plain-language.md. Apply it to every authored field and live
reply.
Steps
Read the source spec/plan (the file the user names, or the plan already in context).
Read the authoritative schema: $VISUAL_SKILLS_DIR/src/blocks.ts. It defines the
Block union — treat it as the source of truth for field names and shapes.
Ground it in the real repo: use real file paths and real identifiers from the target
codebase (e.g. model names, route/procedure names, exported symbols). Do not invent
identifiers.
Author a Block[] JSON array using the mapping below.
Render it from the tool directory:
cd "$VISUAL_SKILLS_DIR"
npx tsx bin/doc.ts --blocks <ABSOLUTE_BLOCKS_JSON> --title "<Title>" \
--source "<source path or label>" --out <ABSOLUTE_OUT_DIR>
--out is a directory (e.g. <repo>/.visual/docs/<label>, absolute path); the HTML
(doc.html) and any .excalidraw sidecars are written together inside it. Diagrams render as
static D2 by default and stay that way unless you opt in: pass --excalidraw to promote
editable-eligible diagrams to .excalidraw scenes (requires the npm run setup:excalidraw
toolchain). Without the flag — or with --no-excalidraw — you get the static D2 floor.
Open it: open <ABSOLUTE_OUT_DIR>/doc.html (macOS), else report the path.
Content -> block mapping
For diagram selection (which structural / boundary / data-flow / behavioral diagram to use) and
compile-tested recipes, consult the shared catalog: $VISUAL_SKILLS_DIR/skills/shared/diagrams.md.
Color diagrams with the catalog's semantic palette (the "Color vocabulary" section) — mark the
changed/subject node and tag actors / external systems / datastores by role.
Primary blocks you author for a doc:
narrative / sections -> prose (Markdown; GitHub-flavored). A fenced mermaid
flowchart inside prose is auto-promoted to a diagram (and becomes editable if the
Excalidraw upgrade is installed).
{ "type": "prose", "id": "overview", "markdown": "## Overview\n\nWhat & why..." }
architecture / flow -> diagram — d2 is required (the rendering floor); add
mermaid for the editable upgrade on flowchart/architecture kinds. Quote any d2
key/value containing a dot or space.
{ "type": "diagram", "id": "flow", "title": "Request flow", "kind": "flowchart",
"d2": "direction: down\n\"client\" -> \"api\" -> \"db\"",
"mermaid": "graph TD\nclient-->api-->db" }
affected / new files -> file-tree — status is one of A/M/D/R.
{ "type": "file-tree", "id": "files", "title": "Files", "files": [
{ "path": "src/server/routers/league.ts", "status": "M", "added": 20, "deleted": 4 } ] }
key code to explain -> annotated-code — per-line notes; use for the 2-3 most
important snippets, not everything. line is 1-based.
{ "type": "annotated-code", "id": "capture", "title": "captureOrder", "lang": "ts",
"code": "const order = await paypal.capture(id);\nreturn order;",
"annotations": [ { "line": 1, "note": "server-side capture" } ] }
open decisions -> questions
{ "type": "questions", "id": "open", "title": "Open questions", "questions": [
{ "question": "Refund window?", "recommendedDefault": "30 days" } ] }
grouping -> group — a titled, collapsible set of related blocks; add an optional description
(markdown) summarizing what the group covers. Shape: { "type":"group", "id":"…", "title":"…", "blocks":[ … ] } (one level deep). Used mainly by recaps to order diffs into a
narrative; available for any doc too.
multiple views of one thing -> tabs — a CSS-only tab switcher (no JS) presenting
complementary diagrams as switchable panels. Each tab holds ONE block, one level deep (a tab
may not contain a group or another tabs).
{ "type": "tabs", "id": "views", "title": "Two views", "tabs": [
{ "label": "Flow", "block": { "type": "diagram", "id": "v-flow", "title": "Flow", "kind": "flowchart", "d2": "a -> b" } },
{ "label": "Seq", "block": { "type": "diagram", "id": "v-seq", "title": "Seq", "kind": "sequence", "d2": "shape: sequence_diagram\na -> b: hi" } } ] }
lead summary -> overview — a scannable callout placed first: a one-line headline, short
points (each href linking to a section by #id), and an optional lead diagram
(DiagramBlock or tabs) rendered before the points. Author it for larger docs.
{ "type": "overview", "id": "overview", "headline": "Add PayPal capture",
"points": [ { "text": "new `capture` [route](#flow)" } ],
"diagram": { "type": "diagram", "id": "ov-flow", "title": "Flow", "kind": "flowchart", "d2": "a -> b" } }
worked instance -> example — a real input walked through stages to its output (source +
lesson required; static rail by default, "reveal"/"step" for volume, variant:"contrast"
for old-vs-new). See skills/shared/spec-components.md for the full recipe.
{ "type": "example", "id": "ex-capture", "title": "Capturing an authorized order",
"source": "test/fixtures/orders/authorized.json",
"stages": [
{ "label": "Input", "kind": "input", "body": "…" },
{ "label": "Output", "kind": "output", "body": "…" } ],
"lesson": "Capture is idempotent — replaying it twice charges once." }
When to add an example: any transformation or algorithm the doc explains gets one worked
instance — don't just describe the mechanism in prose, show one real case going through it.
Other block types in the Block union — schema, api, diff — are normally produced
automatically by the visual-recap flow from a real git diff, not hand-authored. Reach
for visual-recap when the subject is a code change rather than a plan.
Notes
- Block
ids must be unique across the document.
- Keep d2 valid: quote keys/values with dots (e.g.
"league.captureOrder"). If d2 fails to
compile, that block renders a visible placeholder rather than breaking the document.
- Diagrams need
d2 on PATH (brew install d2); without it they show placeholders.
Example
cd "$VISUAL_SKILLS_DIR"
npx tsx bin/doc.ts --blocks /tmp/doc-blocks.json --title "Payments migration" \
--source docs/specs/payments.md --out /Users/me/Projects/app/.visual/docs/payments
open /Users/me/Projects/app/.visual/docs/payments/doc.html
1---2name: visual-doc3description: Use when the user asks to turn a spec, plan, or design markdown into a self-contained, visually readable HTML document grounded in the real codebase — with diagrams, a file tree, annotated code, and open questions. The general-purpose illustrated-doc renderer (for an approval-focused design spec use visual-spec; for a code change use visual-recap).4---56# Visual Doc78Turn a spec/plan into a single self-contained, hand-drawn-styled HTML document by authoring9a typed block array and rendering it. Unlike the recap (which is automatic), you compose the10blocks — so ground every reference in the real repo.1112**Tool location** (resolved through the installer's `~/.claude/visual-skills` symlink — re-run `npm run skills:install` if the repo moves):1314 VISUAL_SKILLS_DIR="${CLAUDE_CONFIG_DIR:-$HOME/.claude}/visual-skills"1516**Required language guide.** Before writing user-facing text, read17`$VISUAL_SKILLS_DIR/skills/shared/plain-language.md`. Apply it to every authored field and live18reply.1920## Steps21221. **Read the source** spec/plan (the file the user names, or the plan already in context).232. **Read the authoritative schema:** `$VISUAL_SKILLS_DIR/src/blocks.ts`. It defines the24 `Block` union — treat it as the source of truth for field names and shapes.253. **Ground it in the real repo:** use real file paths and real identifiers from the target26 codebase (e.g. model names, route/procedure names, exported symbols). Do not invent27 identifiers.284. **Author a `Block[]` JSON array** using the mapping below.295. **Render it** from the tool directory:3031 cd "$VISUAL_SKILLS_DIR"32 npx tsx bin/doc.ts --blocks <ABSOLUTE_BLOCKS_JSON> --title "<Title>" \33 --source "<source path or label>" --out <ABSOLUTE_OUT_DIR>3435 `--out` is a *directory* (e.g. `<repo>/.visual/docs/<label>`, absolute path); the HTML36 (`doc.html`) and any `.excalidraw` sidecars are written together inside it. Diagrams render as37 static D2 by default and stay that way unless you opt in: pass `--excalidraw` to promote38 editable-eligible diagrams to `.excalidraw` scenes (requires the `npm run setup:excalidraw`39 toolchain). Without the flag — or with `--no-excalidraw` — you get the static D2 floor.40416. **Open it:** `open <ABSOLUTE_OUT_DIR>/doc.html` (macOS), else report the path.4243## Content -> block mapping4445For diagram selection (which structural / boundary / data-flow / behavioral diagram to use) and46compile-tested recipes, consult the shared catalog: `$VISUAL_SKILLS_DIR/skills/shared/diagrams.md`.4748Color diagrams with the catalog's semantic palette (the "Color vocabulary" section) — mark the49`changed`/subject node and tag actors / external systems / datastores by role.5051Primary blocks you author for a doc:5253- **narrative / sections -> `prose`** (Markdown; GitHub-flavored). A fenced `mermaid`54 flowchart inside prose is auto-promoted to a diagram (and becomes editable if the55 Excalidraw upgrade is installed).5657 { "type": "prose", "id": "overview", "markdown": "## Overview\n\nWhat & why..." }5859- **architecture / flow -> `diagram`** — `d2` is required (the rendering floor); add60 `mermaid` for the editable upgrade on `flowchart`/`architecture` kinds. Quote any d261 key/value containing a dot or space.6263 { "type": "diagram", "id": "flow", "title": "Request flow", "kind": "flowchart",64 "d2": "direction: down\n\"client\" -> \"api\" -> \"db\"",65 "mermaid": "graph TD\nclient-->api-->db" }6667- **affected / new files -> `file-tree`** — `status` is one of `A`/`M`/`D`/`R`.6869 { "type": "file-tree", "id": "files", "title": "Files", "files": [70 { "path": "src/server/routers/league.ts", "status": "M", "added": 20, "deleted": 4 } ] }7172- **key code to explain -> `annotated-code`** — per-line notes; use for the 2-3 most73 important snippets, not everything. `line` is 1-based.7475 { "type": "annotated-code", "id": "capture", "title": "captureOrder", "lang": "ts",76 "code": "const order = await paypal.capture(id);\nreturn order;",77 "annotations": [ { "line": 1, "note": "server-side capture" } ] }7879- **open decisions -> `questions`**8081 { "type": "questions", "id": "open", "title": "Open questions", "questions": [82 { "question": "Refund window?", "recommendedDefault": "30 days" } ] }8384- **grouping -> `group`** — a titled, collapsible set of related blocks; add an optional `description`85 (markdown) summarizing what the group covers. Shape: `{ "type":"group", "id":"…", "title":"…", "blocks":[ … ] }` (one level deep). Used mainly by recaps to order diffs into a86 narrative; available for any doc too.8788- **multiple views of one thing -> `tabs`** — a CSS-only tab switcher (no JS) presenting89 complementary diagrams as switchable panels. Each tab holds ONE block, one level deep (a tab90 may not contain a `group` or another `tabs`).9192 { "type": "tabs", "id": "views", "title": "Two views", "tabs": [93 { "label": "Flow", "block": { "type": "diagram", "id": "v-flow", "title": "Flow", "kind": "flowchart", "d2": "a -> b" } },94 { "label": "Seq", "block": { "type": "diagram", "id": "v-seq", "title": "Seq", "kind": "sequence", "d2": "shape: sequence_diagram\na -> b: hi" } } ] }9596- **lead summary -> `overview`** — a scannable callout placed first: a one-line `headline`, short97 `points` (each `href` linking to a section by `#id`), and an optional lead `diagram`98 (`DiagramBlock` or `tabs`) rendered before the points. Author it for larger docs.99100 { "type": "overview", "id": "overview", "headline": "Add PayPal capture",101 "points": [ { "text": "new `capture` [route](#flow)" } ],102 "diagram": { "type": "diagram", "id": "ov-flow", "title": "Flow", "kind": "flowchart", "d2": "a -> b" } }103104- **worked instance -> `example`** — a real input walked through stages to its output (`source` +105 `lesson` required; static rail by default, `"reveal"`/`"step"` for volume, `variant:"contrast"`106 for old-vs-new). See `skills/shared/spec-components.md` for the full recipe.107108 { "type": "example", "id": "ex-capture", "title": "Capturing an authorized order",109 "source": "test/fixtures/orders/authorized.json",110 "stages": [111 { "label": "Input", "kind": "input", "body": "…" },112 { "label": "Output", "kind": "output", "body": "…" } ],113 "lesson": "Capture is idempotent — replaying it twice charges once." }114115**When to add an example:** any transformation or algorithm the doc explains gets one worked116instance — don't just describe the mechanism in prose, show one real case going through it.117118Other block types in the `Block` union — `schema`, `api`, `diff` — are normally produced119automatically by the **visual-recap** flow from a real git diff, not hand-authored. Reach120for visual-recap when the subject is a code change rather than a plan.121122## Notes123124- Block `id`s must be unique across the document.125- Keep d2 valid: quote keys/values with dots (e.g. `"league.captureOrder"`). If d2 fails to126 compile, that block renders a visible placeholder rather than breaking the document.127- Diagrams need `d2` on PATH (`brew install d2`); without it they show placeholders.128129## Example130131 cd "$VISUAL_SKILLS_DIR"132 npx tsx bin/doc.ts --blocks /tmp/doc-blocks.json --title "Payments migration" \133 --source docs/specs/payments.md --out /Users/me/Projects/app/.visual/docs/payments134 open /Users/me/Projects/app/.visual/docs/payments/doc.html