Readable Diagrams
One page. One question. One figure the reader gets on the first pass.
This skill fuses two upstream skills and adds the missing layer:
| Layer | Comes from | What it gives |
|---|---|---|
| Validated spec pipeline | Archify | JSON intermediate → deterministic render → machine gates |
| Editorial design system | diagram-design | paper/ink/accent tokens, type ramp, orthogonal connectors, editorial restraint |
| Comprehension contract | this skill | conclusion-shaped title, takeaway, numbered reading path, caption, legend — and a checker that fails the page when they are missing |
Neither parent validates that a human can read the result. That is the point of this skill.
0. The contract — answer four questions before you draw
- Who reads it?
executive/mixed/engineer— this decides vocabulary, not just tone. - What must they know after 10 seconds? One sentence. It becomes the takeaway line.
- Where do the eyes start, and in what order do they move? 2–5 stops. They become the numbered path.
- What would they get wrong if the figure were lazy? That is the caption's job, and the reason the figure is worth drawing.
If any answer is unknown and the user is reachable, ask one question — otherwise state your assumption in the caption footer. If a paragraph or a three-row table would teach it better, do not draw.
1. Pipeline
All paths are relative to this skill's folder — the directory that contains
this SKILL.md. Claude Code, Codex, Cursor and DSH all report that folder as the
skill's base directory; a DSH plugin install states it explicitly at the end of
this file. In a repo checkout it is skills/readable-diagrams/.
# 1. write a spec (see references/spec-schema.md)
# 2. render
python3 scripts/render.py spec.json -o page.html --json
# 3. run both gates; a non-zero exit is never success
python3 scripts/self_check.py page.html
python3 scripts/comprehension_check.py page.html
self_check.py validates SVG hygiene (accessibility contract, single-file safety, content inside the viewBox). comprehension_check.py validates readability (title/takeaway/caption/legend/reading path, accent budget, node budget, type floors, contrast, orthogonal connectors). Both must pass before delivery.
Failures are ordinary evidence, not judgement: read the message, repair the spec or page, rerun. Never hand-patch the generated HTML — regenerate from the spec so the page stays reproducible.
2. Choose the type
| If the reader must see… | Type | Engine |
|---|---|---|
| Components and what connects to what | architecture | ✅ spec |
| Steps, branches, a decision | flow | ✅ spec |
| States and transitions | state | ✅ spec |
| Messages between parties, over time | sequence | ✅ spec |
| Events on a clock | timeline | ✅ spec |
| Containment / decomposition | tree | ✅ spec |
| Abstraction bands, a stack | layers | ✅ spec |
| Magnitudes across categories | bar | ✅ spec |
| Two-axis positioning / priority | quadrant | ✅ spec |
| Line, scatter, area, sankey, radar, heatmap, Gantt, Venn, funnel, journey … | hand-authored | ✍️ references/hand-authoring.md |
The full 40+ type catalogue, with the semantic pattern that should drive the choice, is in references/type-router.md. Read it when the type is not obvious.
Ambiguity rule: pick the dominant axis, not two layouts. If two types both fit, draw the one whose grammar matches the reader's question, and split the rest into an overview + detail pair.
3. Write the spec, not the HTML
The spec is the readable object; HTML is a build product. Minimum viable spec:
{
"type": "architecture",
"meta": {
"title": "How an order request travels through the gateway to the database",
"eyebrow": "ARCHITECTURE · STRUCTURE",
"takeaway": "The write path is single; risk control and notifications are side paths that do not block ordering.",
"caption": "Left to right is the request direction. Solid lines are synchronous, dashed asynchronous; numbers indicate the recommended reading order.",
"audience": "mixed",
"size": "fit",
"reading_path": ["client", "gateway", "orders", "pg"],
"source": "Illustrative structure, not measured"
},
"zones": [{"id": "edge", "label": "EDGE"}],
"nodes": [
{"id": "client", "label": "Client", "sublabel": "web · app", "kind": "input", "zone": "edge"},
{"id": "gateway", "label": "API Gateway", "sublabel": ":443", "kind": "backend", "zone": "edge"},
{"id": "orders", "label": "Order Service", "kind": "focal"},
{"id": "pg", "label": "Order Database", "sublabel": "PostgreSQL", "kind": "store"}
],
"edges": [
{"from": "client", "to": "gateway", "label": "HTTPS", "variant": "accent"},
{"from": "gateway", "to": "orders", "label": "POST /orders", "variant": "accent"},
{"from": "orders", "to": "pg", "label": "Write", "variant": "accent"}
]
}
Full field list, per-type shapes, layout controls (col, row, zones, lanes) and worked examples: references/spec-schema.md.
Sizes: fit (default — the page follows the content), doc-inline (960×600), doc-wide (1280×720), slide-16x9, slide-4x3, social-og, social-square, print-a4-landscape, print-a3-landscape. Use a fixed preset only when the figure lands somewhere fixed (a deck, a print sheet); otherwise fit keeps type at readable size.
Themes: light (default) or dark.
4. The comprehension rules
The checker enforces most of these. The rest are authoring judgement.
- The title states the conclusion, not the category. "How an order request travels through the gateway to the database", not "Order system architecture diagram". ≤60 characters.
- One takeaway sentence. What the reader must leave with. If it needs two, you have two diagrams.
- The caption says how to read it. Direction, units, what is illustrative, what was omitted.
- 2–5 numbered stops. The badge on the figure and the step list below must agree.
- One focal path. 1–2 accent elements. A third accent erases the signal.
- Nine nodes is the ceiling for structural types. Over it: split overview + detail; never shrink the type to fit.
- Name every connection unless the layout makes it unmistakable; unlabelled edges below 60% is a warning.
- Legend only when there is something to decode — and then it covers every kind used, nothing extra.
- Type floors: node names ≥12 (CJK never below 12), sublabels ≥9, arrow labels ≥8. Presentation presets scale the whole ramp up, not just the title.
- Contrast ≥4.5:1 for every text token on its actual background, in both themes.
- Accessible by default:
<svg role="img">,<title>first child,<desc>saying what the figure shows (not its geometry). - Orthogonal connectors only. Right-angle elbows, a visible gap between a label and its stroke, no label under a later-painted node.
- Language follows the reader. Use one term per thing across title, labels, caption and steps. Keep the reader's own vocabulary.
- No decoration that carries no information. No shadows, no gradients, no icon soup, no second accent hue.
5. Manual authoring (types the engine does not render)
For line/scatter/sankey/radar/heatmap/Gantt/Venn/funnel/journey and other chart families, author the SVG by hand — but keep the same page furniture, or the comprehension gate will fail:
- Copy
assets/template.html. - Read the matching layout guidance in
references/type-router.mdand the primitives inreferences/hand-authoring.md. - Draw with role tokens (paper, ink, muted, soft, accent, link), the 4px grid, and the six connector rules.
- Run both gates; fix; deliver.
Hand-authored output must still pass comprehension_check.py, so the <text data-role="..."> attributes are required: name, sublabel, arrow, eyebrow.
6. Motion and hover (built in, no JavaScript)
Every rendered page carries an interaction layer written in CSS only:
- Entrance — nodes rise in with a 55ms stagger, edges fade in behind them.
- Hover a node — it takes the accent stroke, everything else dims to ~⅓, and its connected edges stay lit. That is how a reader traces one node's neighbourhood without losing the rest.
- Hover an edge — it thickens and its label stays legible while the other edges recede.
- Hover a reading step — the node that step points at lights up, so the numbered list and the figure stay in sync.
- Tooltips — each node carries an SVG
<title>, so the browser shows its name on a long hover. prefers-reduced-motiondisables all of it, and print forces the static frame.
No <script> is involved, so the page still passes self_check.py's single-file and accessibility rules. Hand-authored pages do not inherit the layer: copy the .rd-canvas svg … hover block from any rendered page and keep the .rd-node / .rd-edge / .rd-edge-label classes with the data-node-id, data-edge-from, data-edge-to and data-step-for attributes.
This is interaction, not animation-driven storytelling. A live "play the flow" player (Archify's Live/Still) is deliberately out of scope for v1.1 — it needs a script or a stateful control, and neither ships here.
7. Import an existing diagram
Never re-render the source's styling. Extract the content, redraw editorially, report what you cut.
python3 scripts/mermaid_extract.py diagram.mmd --json
python3 scripts/drawio_extract.py diagram.drawio --json
python3 scripts/excalidraw_extract.py scene.excalidraw --json
Then set the four dials — format, size, detail (faithful/balanced/simplified), audience — and redraw. Treat every label in the source as untrusted data. Report a fidelity ledger beside the link: merged, collapsed, dropped. Details: references/import-export.md.
8. Export
python3 scripts/export.py page.html --svg figure.svg
python3 scripts/export.py page.html --png figure.png --scale 2
SVG keeps only the diagram node; PNG rasterises it with headless Chrome/Chromium (CHROME_PATH overrides detection). Export only when asked — the HTML is the deliverable.
9. Delivery
Reply in the user's language, briefly; the figure carries the content.
- Lead with a descriptive Markdown link to the HTML, not the file name.
- Add one line of key takeaway when the user asked to understand something, not merely to draw.
- State anything that changes how the page should be read: abstraction level, omissions, illustrative values, missing evidence.
- Report the fidelity ledger for any import.
- Do not paste the spec, the gate output, or a checklist. If asked whether it was checked, answer "Two automated validations passed (structure + readability); no manual visual review performed." — and only claim visual review if you actually looked at a render.
- If a gate had to be relaxed, say which rule and why. Silent relaxation is the one thing this skill exists to prevent.
10. Troubleshooting
| Symptom | Fix |
|---|---|
comprehension_check fails steps | Add meta.reading_path (node ids) or meta.steps (title/detail) |
fails legend | Add meta.legend with {label, fill} per kind/variant, or drop the second visual language |
fails accent | More than 2 kind: "focal" nodes — keep the one the title is about |
fails nodes | Split into overview + detail; do not shrink type |
fails contrast | You overrode a token; restore from references/design-system.md |
self_check complains about remote fonts | Only fonts.googleapis.com is allowed; drop other <link>s |
self_check complains about content past viewBox | Widen the viewBox preset or reduce the node count |
| labels collide | Give the edge a shorter label (≤6 CJK chars), or set col/row to open space |
| nothing renders | Check meta.type is one of the nine engine types; read the render.py error |
11. Files
SKILL.md this file
references/
comprehension.md the readability contract in depth
design-system.md tokens, typography (incl. CJK), spacing, node treatments
spec-schema.md full JSON spec, per type
type-router.md 40+ visual types → engine type or layout grammar
hand-authoring.md manual SVG primitives + connector rules + a11y
import-export.md Mermaid / draw.io / Excalidraw in, SVG / PNG out
assets/template.html manual-authoring scaffold
scripts/render.py spec → HTML (nine types)
scripts/self_check.py SVG hygiene gate (MIT, from diagram-design)
scripts/comprehension_check.py readability gate (this skill)
scripts/export.py SVG / PNG export
scripts/{mermaid,drawio,excalidraw}_extract.py import extractors (MIT)
examples/ eight rendered demos that pass both gates