feat(import): redraw draw.io files at a chosen format, size, and detail level

Adds a draw.io import path to the skill. Points at a .drawio file and
produces an editorial diagram fitted to where it's going — not a
format conversion.

- scripts/drawio_extract.py: decodes all four containers draw.io writes
  (raw XML, deflate+base64 payloads, PNG tEXt chunks, SVG content attrs),
  flattens mxGraphModel into absolute-positioned nodes/edges, and reports
  structural signals: hubs, containers, depth, cycles, collapsible leaf
  groups, budget flags. Deterministic half — makes no design decisions.
- references/output-spec.md: the three dials. Nine size presets with
  per-size-class type ramps and safe areas; faithful/balanced/simplified
  detail levels with a fixed degrade ladder; engineer/mixed/executive
  audience wording; fidelity ledger; CJK font fallback.
- references/import-drawio.md: the redraw procedure — extract, set dials,
  pick type, build the semantic model, redraw on the grid. Includes the
  draw.io palette/shape → semantic role mappings, multi-page handling,
  edge cases, and anti-patterns.
- commands/import-drawio.md: /diagram-design:import slash command.
- SKILL.md: new §11 (import + output dials), taste-gate entries, §12 output.
- export.md: sizing section — scale factors and exact-pixel targets.
- assets/example-import-drawio.html: worked import of the checked-in
  fixture at balanced/doc-inline, lint-clean.
- scripts/verify-drawio-import.py: drives the real extractor against the
  fixture in all four containers and checks the references stay in sync.

faithful is the one documented exemption from the §7 complexity budget,
and it's conditional: zoned above 9 nodes, split above 24. The §6
connector rules never relax for imported content.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01M5f1aK1zNcgnQUnXZgG2LU
This commit is contained in:
Claude
2026-08-11 13:51:30 -07:00
committed by Cathryn Lavery
parent 9fe1339f14
commit ced802198d
14 changed files with 1873 additions and 10 deletions
@@ -92,6 +92,32 @@ Default `device_scale_factor=2` for crisp output. Accept `1` for compact assets
`example-architecture.html` → `example-architecture.png`, written next to the source. Honour explicit user-provided paths.
## Sizing the export
The PNG's pixel dimensions are the SVG's `viewBox` × `device_scale_factor`. So the size decision was already made when the diagram was drawn — see [`output-spec.md` §2](output-spec.md) for the presets. Export only picks the multiplier.
| Destination | Scale | Result from a 1280×720 `viewBox` |
|---|---|---|
| Docs, README, wiki | 2 | 2560×1440 |
| Slide deck (projected) | 2 | 2560×1440 |
| Print / PDF handout | 3 | 3840×2160 |
| Inline thumbnail, email | 1 | 1280×720 |
### Hitting an exact pixel size
When the user needs specific dimensions (an OG card at exactly 1200×630, a slide image at 1920×1080), compute the scale factor instead of guessing — Playwright accepts fractional values:
```
scale = target_width / viewBox_width
```
A 960-wide `viewBox` at a 1200px target is `scale=1.25`. Two rules:
- **Never scale below 1** to hit a small target — that soft-focuses the type. Redraw at a smaller preset instead.
- **Never scale past 4** — beyond that you're upscaling a layout that was designed for a smaller canvas; redraw at `slide-16x9` or a print preset.
If the target aspect ratio doesn't match the `viewBox` aspect ratio, say so and offer to redraw at the matching preset. Padding or cropping a finished diagram to fit a frame is not an export operation — it breaks the 40px safe margin.
## Edge cases
- **Source is `assets/index.html`** (the gallery, multiple SVGs in one file): refuse the export and ask the user which specific diagram file they meant. Don't guess.
@@ -0,0 +1,169 @@
# Import from draw.io
Turn a `.drawio` file into an editorial-quality diagram at the format, size, and detail level the destination needs.
**This is a redraw, not a conversion.** You read the source for its *content* — components, relationships, grouping, direction — and then draw a new diagram in this skill's design system. Nothing about the source's geometry, palette, or shape vocabulary carries over. A converter that preserved draw.io's layout would just be draw.io output with different fonts.
## Trigger
Load this file when the user points at a `.drawio`, `.drawio.xml`, `.drawio.png`, or `.drawio.svg` file and wants a diagram out of it — "convert this drawio", "redraw this diagram", "make this presentable", "この drawio をきれいにして", or the `/diagram-design:import` slash command.
---
## Step 1 — Extract the IR
Never read a `.drawio` file with Read. Most are deflate+base64 payloads, and even the readable ones are 10× more XML than signal. Run the extractor:
```bash
python3 <skill-dir>/scripts/drawio_extract.py <file> [--page N|NAME|all]
```
`<skill-dir>` is `skills/diagram-design/` in this repo, or the skill's own directory when it's installed standalone or as a plugin. If the path isn't obvious, glob for `**/diagram-design/scripts/drawio_extract.py`.
It handles all four containers draw.io writes — raw XML, compressed `<diagram>` payloads, PNG with an embedded `mxfile` chunk, and SVG with a `content` attribute — and prints a Markdown digest: node/edge tables with absolute geometry, shape classes, hub degrees, container structure, cycle detection, budget flags, and *collapsible groups* (the first things to merge when compressing).
Options worth knowing:
- `--page all` — multi-page files. Default is page 0 only; the header line lists every page with its node/edge counts.
- `--json` — full IR when the digest truncated something you need (every style value, every waypoint).
- `--max-rows N` — digest table length, default 40.
Read the digest, not the file. If the digest is empty (`0 nodes`), the source is an image-only or encrypted file — see *Edge cases*.
## Step 2 — Set the three dials
Before drawing, fix format, size, and detail level per [`output-spec.md`](output-spec.md). Ask once for whatever the user didn't specify, and let the digest inform the options you offer:
> *"18 nodes in 3 groups. Where's this going — slide, blog post, or hand-off? And should I keep every component or compress to the request path?"*
The digest's `budget:` line tells you whether the ask is even possible: a source over the node budget cannot go to `slide-16x9` at `faithful` without splitting. Say so at this step rather than after drawing.
## Step 3 — Pick the target type
The source's shape vocabulary is a hint, not an instruction. draw.io users reach for rectangles because rectangles are what's on the toolbar.
| Digest signal | Likely type | Reference |
|---|---|---|
| `lifeline` shapes, tall vertical bars | Sequence | [type-sequence.md](type-sequence.md) |
| `table` / `er` shapes, rows of fields | ER / data model | [type-er.md](type-er.md) |
| ≥2 aligned `swimlane` containers (`type candidates: swimlane`) | Swimlane | [type-swimlane.md](type-swimlane.md) |
| `rhombus` present, single entry point, labeled yes/no edges | Flowchart | [type-flowchart.md](type-flowchart.md) |
| Mostly `ellipse`, self-loops, `has_cycle: True` | State machine | [type-state.md](type-state.md) |
| `icon:aws` / `icon:azure` / `icon:gcp` / `icon:kubernetes` families | Architecture | [type-architecture.md](type-architecture.md) |
| Nested containers, depth ≥2, few edges | Nested | [type-nested.md](type-nested.md) |
| One entry point, no cycle, fan-out only | Tree or Org chart | [type-tree.md](type-tree.md), [type-org-chart.md](type-org-chart.md) |
| Boxes stacked vertically, edges only between neighbours | Layer stack | [type-layers.md](type-layers.md) |
| Dated labels on a single axis | Timeline or Gantt | [type-timeline.md](type-timeline.md), [type-gantt.md](type-gantt.md) |
| Anything else with edges | Architecture | [type-architecture.md](type-architecture.md) |
The digest's `type candidates` field ranks these mechanically. Override it when the content disagrees — a "flowchart" whose diamonds all ask *"which service?"* is an architecture diagram someone drew with the wrong shapes. Tell the user when you override, in one line.
**Load the chosen `type-*.md` before drawing.** Its layout conventions win over anything the source did.
## Step 4 — Build the semantic model
Work from the digest, not from coordinates. In order:
1. **Name the story.** One sentence: *"A request enters through the gateway, gets authenticated, and lands in Postgres."* Everything that doesn't serve that sentence is a degrade-ladder candidate.
2. **Apply the detail level.** Walk [`output-spec.md` §3](output-spec.md) degrade ladder until you're under the node ceiling. The digest's *collapsible groups* section is step 3 of that ladder, pre-computed.
3. **Pick 1–2 focal nodes.** The digest's `hubs` ranking (highest degree) is the usual answer, but the focal node is the one the *reader* should look at first — sometimes that's the entry point or the new component, not the busiest one. These get `accent`; everything else does not.
4. **Rewrite every label** at the audience level ([`output-spec.md` §4](output-spec.md)). draw.io labels are written by the author for the author: `svc-auth-prod-v2` becomes `Auth Service`. Preserve proper nouns, expand acronyms once.
5. **Prune edges.** Source graphs carry edges that layout already implies. If A sits above B in a stack and everything flows down, the arrow is noise. Keep edges that carry a label, cross a zone boundary, or run against the dominant direction.
## Step 5 — Redraw
Fresh layout on the 4px grid, per the type reference and SKILL.md §6–§7. Explicitly:
- **Discard source coordinates.** draw.io positions are hand-dragged and land on odd pixels. Lay out from scratch: dominant flow left→right (or top→bottom), zones aligned, even gaps.
- **Discard source colors.** Map them to semantic roles instead:
| draw.io default fill | Typical meaning | Maps to |
|---|---|---|
| `#dae8fc` / `#6c8ebf` (blue) | generic component | Backend/API — white fill, `ink` stroke |
| `#d5e8d4` / `#82b366` (green) | ok / primary path | `ink` treatment; accent **only** if focal |
| `#ffe6cc` / `#d79b00` (orange) | attention / queue | `ink` treatment; accent only if focal |
| `#f8cecc` / `#b85450` (red) | failure / risk / legacy | Optional/Async — dashed `ink @ 0.20` |
| `#e1d5e7` / `#9673a6` (purple) | external / third-party | External/Cloud — `ink @ 0.03` fill |
| `#f5f5f5` / grey | infrastructure / background | Store/State, or a zone container |
| no fill | unstyled | Backend/API |
Source color is a *signal about role*, not a color to keep. Six fill colors in the source do not become six fills in the output — the palette is one accent plus the ink ramp (SKILL.md §5).
- **Map shapes to treatments**, not to lookalikes:
| Source shape | Draw as |
|---|---|
| `cylinder` | Store/State box (`ink @ 0.05` fill, `muted` stroke) — not a 3-D barrel |
| `rhombus` | Flowchart decision diamond, only in a flowchart; elsewhere a normal box |
| `actor` | Input/User treatment, or the user icon from [primitive-icons.md](primitive-icons.md) |
| `cloud` | External/Cloud treatment |
| `note` | Annotation callout ([primitive-annotation.md](primitive-annotation.md)), max 2 — or drop |
| `icon:aws` / `icon:azure` / `icon:gcp` / `icon:kubernetes` | The matching monochrome icon from [primitive-icons.md](primitive-icons.md), inheriting `currentColor` |
| `image` (custom PNG/vendor logo) | Nearest icon, or a labeled box. Never re-embed the source image. |
| `text` (floating label) | Drop, or fold into a zone label |
- **Reroute every connector.** Source waypoints are dead weight — the digest reports a waypoint count so you know how tangled the original was, not so you can reproduce it. Rounded orthogonal elbows, fanned attach points, no overlaps: SKILL.md §6 rules 1–5, no exceptions for imported content.
- **Set the `viewBox` from the size preset**, then lay out inside it — don't draw first and crop after.
## Step 6 — Deliver
1. Write the `.html`.
2. Run the SKILL.md §9 taste gate **and** the [`output-spec.md` §6](output-spec.md) checklist.
3. Produce `svg` / `png` if the format dial asked for them — via [`export.md`](export.md), from the HTML.
4. Report the fidelity ledger ([`output-spec.md` §5](output-spec.md)). Every import gets one; the user knows the source and will notice what's gone.
---
## Worked example
[`assets/example-import-drawio.html`](../assets/example-import-drawio.html) is the output of this procedure run on `scripts/fixtures/sample-architecture.drawio` (12 nodes, 8 edges, 2 container groups) at `format=html`, `size=doc-inline`, `detail=balanced`, `audience=mixed`.
What the run decided, and why:
| Source | Output | Reason |
|---|---|---|
| `Edge` + `Core Services` swimlane containers | `EDGE` / `CORE SERVICES` zone frames | Containers became zones, not boxes — they group, they don't act |
| Postgres, Redis, Object Store scattered down the right | One `DATA` zone in a bottom row | Regrouping by role removed every connector crossing |
| `Token valid?` decision diamond | The `VERIFY` label on Gateway → Auth | A single decision inside an architecture diagram is an edge label |
| Sticky note "Legacy path, to be retired" | Dropped | Unconnected in the source; step 1 of the degrade ladder |
| `#dae8fc` / `#d5e8d4` / `#e1d5e7` fills | White services, ink-tint stores, one accent | Source color signals role; roles map to the design system |
| API Gateway (degree 4, the digest's top hub) | The one accent node | Highest-degree node was also the story's pivot |
12 source nodes → 8 drawn, inside the standard §7 budget even at a level that allows 12.
---
## Multi-page files
Default is page 0. When the file has several pages:
- **Ask which page** unless the user named one. List them from the digest header — names and node counts.
- `--page all` when they want everything: one HTML file per page, named `<base>-<page-name>.html`, each independently type-selected. Pages in one draw.io file are frequently different diagram types.
- Don't merge pages into one canvas unless asked. A 3-page file merged is a 40-node fail.
## Edge cases
| Situation | Do |
|---|---|
| Digest shows `0 nodes` | The source is an image-only export or encrypted (`<mxfile ... type="embed">` with no readable model). Tell the user; ask for the original `.drawio` or a description. Don't guess from a screenshot. |
| Extractor exits 2 | Report the message verbatim — it names the actual problem (not a draw.io file / malformed XML / no pages). Don't fall back to reading the raw file. |
| `edges_dangling > 0` | Edges whose endpoints were deleted in the source. Drop them silently — they're source rot, not content. |
| Unconnected nodes listed | Usually legends, titles, or abandoned boxes. Drop unless the label says otherwise; mention in the ledger if it looked meaningful. |
| Labels are empty across the board | The source carries meaning in shape and position only. Ask the user what the boxes are — don't invent names. |
| Source has 40+ nodes | Don't offer `faithful`. Propose overview + per-zone detail up front, before drawing anything. |
| Source is someone else's branded diagram | Redraw in the *project's* skin (`style-guide.md`), not the source's. Say so — it's a feature, not a bug. |
| CJK / non-Latin labels | Font fallback per [`output-spec.md` §4](output-spec.md). Don't romanize labels. |
## Anti-patterns
| Anti-pattern | Why it fails |
|---|---|
| Reproducing source coordinates | Imports draw.io's hand-dragged layout — off-grid, uneven gaps, the exact thing this skill exists to fix |
| Keeping the source palette | Six pastel fills read as six meanings; the design system has one accent |
| One-to-one node mapping regardless of budget | A 30-node canvas is a wiring diagram nobody reads |
| Keeping every edge because it was in the source | Source graphs carry edges layout already implies |
| Copying labels verbatim | `svc-auth-prod-v2` is a hostname, not a name a reader can use |
| Re-embedding vendor logos from the source | Breaks the self-contained rule and the monochrome icon system |
| Silently dropping components | The user knows the source. Always ship the fidelity ledger. |
| Inventing components to fill a layout | An import is bounded by its source. Gaps get asked about, not filled. |
| Preserving draw.io diagonal connectors | Orthogonal elbows are mandatory (SKILL.md §6 rule 1) regardless of origin |
@@ -0,0 +1,179 @@
# Output spec — format × size × detail level
Three dials decide what a diagram becomes. Set all three **before** drawing — they change the layout, the type ramp, and the node count, so retrofitting them afterwards means redrawing.
| Dial | Question it answers | Default |
|---|---|---|
| **Format** | Where does this file land? | `html` |
| **Size** | How big is the canvas, and how far away is the reader? | `doc-inline` |
| **Detail level** | Reproduce every element, or compress for the audience? | `balanced` + `mixed` |
Ask the user for any dial they didn't specify — one question, three options each, not three separate rounds. If they decline to choose, use the defaults above and say which ones you used.
---
## 1. Format
| Format | Deliverable | Keeps | Drops |
|---|---|---|---|
| `html` | self-contained `.html` (default) | header, diagram, summary cards, footer, live fonts | nothing |
| `svg` | `.svg` next to the source | the `<svg>` node, vector text | editorial wrapper; fonts substitute in offline tools |
| `png` | `.png` at `device_scale_factor` | pixels exactly as the browser renders them | vector editability |
| `html+png` | both | — | — |
Always generate the HTML first — `svg` and `png` are produced *from* it via [`export.md`](export.md). Never hand-author an SVG file directly; the HTML is the source of truth and the only artifact the taste gate (SKILL.md §9) is written against.
Pick by destination:
| Destination | Format | Size preset |
|---|---|---|
| Blog post, README, docs site | `html` (embed) or `png` | `doc-inline` |
| Keynote / PowerPoint / Google Slides | `png` @2 | `slide-16x9` |
| Figma / Illustrator / further editing | `svg` | `fit` |
| X / LinkedIn / OG link card | `png` @2 | `social-og` |
| Printed handout, PDF deck | `png` @3 | `print-a4-landscape` |
| Confluence / Notion / internal wiki | `png` @2 | `doc-wide` |
---
## 2. Size
The preset sets the SVG `viewBox`. Every value below is divisible by 4, so the grid rule in SKILL.md §7 still holds.
| Preset | viewBox | Aspect | PNG @2 | Type ramp | Use |
|---|---|---|---|---|---|
| `doc-inline` (default) | `0 0 960 600` | 8:5 | 1920×1200 | standard | Body-width diagram in a post or README |
| `doc-wide` | `0 0 1280 720` | 16:9 | 2560×1440 | standard | Full-width docs, wiki pages |
| `slide-16x9` | `0 0 1280 720` | 16:9 | 2560×1440 | presentation | Deck slide, projected |
| `slide-4x3` | `0 0 1024 768` | 4:3 | 2048×1536 | presentation | Legacy deck templates |
| `social-og` | `0 0 1200 632` | ~1.9:1 | 2400×1264 | presentation | Link preview card |
| `social-square` | `0 0 1080 1080` | 1:1 | 2160×2160 | presentation | Feed post, carousel |
| `print-a4-landscape` | `0 0 1120 792` | ~1.41:1 | @3 → 3360×2376 | print | A4 landscape, ~10mm margins at 96dpi |
| `print-letter-landscape` | `0 0 1056 816` | ~1.29:1 | @3 → 3168×2448 | print | US Letter landscape |
| `fit` | derived from content | any | @2 | standard | Vector hand-off; no fixed frame |
### Deriving `fit`
Round the content bounding box **up** to the next multiple of 4, then add the fixed chrome: 40px outer margin on every side, plus 60px at the bottom for the legend strip. Never let the content touch the viewBox edge.
### Type ramp per size class
Node names shrink relative to the canvas as it grows — resist that. Scale the ramp with the preset so a projected slide stays readable from the back row.
| Role | standard | presentation | print |
|---|---|---|---|
| Title (Instrument Serif) | 28 | 40 | 32 |
| Node name (Geist 600) | 12 | 16 | 12 |
| Sublabel (Geist Mono) | 9 | 12 | 9 |
| Arrow label (Geist Mono) | 8 | 12 | 8 |
| Eyebrow / tag (Geist Mono) | 8 | 8 | 8 |
| Node box min height | 48 | 64 | 48 |
| Min gap between nodes | 24 | 40 | 24 |
Presentation ramp implies fewer nodes — 16px names in 64px boxes eat the canvas. If a `slide-16x9` layout won't fit, that's the size dial telling you the detail dial is set too high; drop a level rather than shrinking the type.
### Safe areas
- **All presets:** 40px outer margin; legend strip is the bottom 60px and nothing else lives there.
- **`social-og`:** keep the outer 64px clear on every side — link-card crops are unpredictable across platforms.
- **`slide-*`:** keep the bottom 80px clear if the deck template has a footer bar; ask if unsure.
---
## 3. Detail level
How much of the source survives. This is a *count* dial — it governs how many elements make it through, not how they're worded (that's §4).
| Level | Nodes | Edges | Sublabels | What survives |
|---|---|---|---|---|
| `faithful` (詳細) | ≤24, zoned | ≤32 | every port, protocol, version | Every distinct component in the source. Only exact duplicates merge. |
| `balanced` (default) | ≤12 | ≤16 | technical sublabel on ≤4 nodes | Components that carry the story; leaf clusters collapse to one node each. |
| `simplified` (簡略) | ≤7 | ≤9 | none | Capabilities and their sequence. Infrastructure disappears. |
`balanced` and `simplified` sit inside the standard complexity budget (SKILL.md §7). **`faithful` deliberately exceeds it** — that's the trade, and it comes with conditions:
1. **Zoning is mandatory.** Above 9 nodes, every node belongs to a labeled zone (2–4 zones, hairline-bordered, `paper-2` fill, mono uppercase zone label at top-left). An unzoned 20-node diagram is a wiring diagram, not a schematic.
2. **Connector rules don't relax.** SKILL.md §6 rules 1–5 still apply at 24 nodes. If you can't route it without overlaps, you're over the real ceiling — split.
3. **Above 24 nodes, split.** Produce an overview (zones as nodes, `balanced` grammar) plus one detail diagram per zone. Name them `<base>-overview.html`, `<base>-<zone>.html`. Never ship a 40-node single canvas.
4. **Accent stays at 2.** More nodes never buys more focal elements.
### Degrade ladder
When the source has more than the level allows, cut in this order and stop as soon as you're under budget. Never cut ad hoc.
1. **Decorative cells** — sticky notes, free-floating text, title blocks, watermarks, the source's own legend. (Notes worth keeping become annotation callouts — max 2, see [primitive-annotation.md](primitive-annotation.md).)
2. **Exact duplicates** — N identical workers/replicas/shards become one node labeled `Worker ×N`.
3. **Leaf clusters** — a container whose children are all leaves collapses to the container: `Core Services` replaces its three boxes. The extractor lists these under *collapsible groups*.
4. **Degree-1 sinks that don't change the story** — a monitoring hook, a log bucket, an archive tier.
5. **Cross-cutting infrastructure** — logging, metrics, secrets, CI. At `simplified` these go without asking; at `balanced` keep at most one, and only if the diagram is about it.
6. **Still over?** Split into overview + detail. Splitting beats shrinking.
Anything cut in steps 2–6 goes in the fidelity ledger (§5). Step 1 doesn't need reporting.
---
## 4. Audience level
Independent of the detail dial: the same 12 nodes get named differently for a platform team than for a steering committee. Detail sets *how many*; audience sets *what they're called*.
| Audience | Node names | Sublabels | Edge labels | Never |
|---|---|---|---|---|
| `engineer` | exact service / component names | protocol, port, version, image tag | `POST /v2/orders`, `SQL`, `gRPC` | Vague verbs like "connects to" |
| `mixed` (default) | component names, expanded acronyms | technology only where it changes a decision | plain verbs — `verifies`, `writes`, `notifies` | Ports, versions, internal codenames |
| `executive` | capabilities and outcomes | none | business verbs — `approves`, `pays out` | Vendor names, infrastructure, protocols |
Worked example — the same node through all three:
| Audience | Node name | Sublabel |
|---|---|---|
| `engineer` | `Auth Service` | `JWT · RS256 · :8443` |
| `mixed` | `Auth Service` | `token check` |
| `executive` | `Sign-in` | — |
Two rules that hold at every audience level:
- **Never invent detail to fill a slot.** If the source says `svc-04`, `executive` output says what it does only if you can tell from context — otherwise ask, don't guess a business name.
- **Keep the source's vocabulary for proper nouns.** Renaming `Kafka` to `Message Bus` is fine at `executive`; renaming it to `Event Grid` (a different product) is a factual error.
### Non-Latin labels
Geist has no CJK coverage. When labels contain Japanese, Chinese, or Korean text, extend the family on those `<text>` elements — don't swap the whole skin:
```svg
<text font-family="'Geist', 'Hiragino Sans', 'Noto Sans JP', 'Yu Gothic', sans-serif">認証サービス</text>
```
For mono sublabels use `'Geist Mono', 'Noto Sans Mono CJK JP', monospace`. CJK glyphs render ~10% wider than Latin at the same size — budget box width accordingly, and prefer 12px names over 8px sublabels for CJK, which goes muddy below 10px.
---
## 5. Fidelity ledger
Any time output is smaller than input — every `balanced` and `simplified` run, and most `faithful` ones — report what you cut, in chat, after the file path. Short and specific:
```
Detail: balanced · 18 source nodes → 9 drawn
Merged: worker-01..06 → "Ingest Worker ×6"
Collapsed: "Observability" group (Grafana, Loki, Tempo) → one node
Dropped: 2 sticky notes, CI pipeline (cross-cutting)
Kept in full: the request path (Client → Gateway → Orders → Postgres)
```
The reader of the diagram can't see what's missing. The person who asked for it needs to.
---
## 6. Checklist
Run alongside the SKILL.md §9 taste gate.
- [ ] All three dials set — explicitly by the user, or defaulted and stated?
- [ ] `viewBox` matches the size preset exactly, values divisible by 4?
- [ ] Type ramp matches the size class — not the standard ramp on a slide?
- [ ] 40px outer margin honoured (64px for `social-og`)?
- [ ] Node count inside the detail level's ceiling?
- [ ] `faithful` above 9 nodes → zoned, and split above 24?
- [ ] Node names, sublabels, and edge labels all at the same audience level?
- [ ] CJK labels given a font fallback?
- [ ] Fidelity ledger reported for anything cut?
- [ ] Requested non-HTML formats produced via [`export.md`](export.md), not hand-authored?