D2 Diagram Author Generate idiomatic source files — diagrams encoded as declarative text, rendered by the official CLI to SVG / PNG / PDF / GIF. The output is human-readable, diffable, and embeds anywhere D2 embeds (GitHub, Notion, VS Code, docs sites, Oxide's play.d2lang.com). Keep this entry file small. Load only the reference packs that match the current diagram. Always-On Rules 1. Output is text, nothing else. The skill never invokes a renderer; the user runs or opens play.d2lang.com. Quality must be enforceable by reading the source. 2. Idiomatic D2 over clever D2. Prefer declarative nod…

\n pass_filenames: false\n```\n\n---\n\n## Docusaurus\n\nOption A — **MDX with build-time render plugin**:\n\n```sh\nnpm install docusaurus-plugin-d2\n```\n\n`docusaurus.config.js`:\n\n```js\nmodule.exports = {\n plugins: [\n ['docusaurus-plugin-d2', {\n layoutEngine: 'elk',\n theme: 0,\n darkTheme: 200,\n }],\n ],\n};\n```\n\nThen in an `.mdx` file:\n\n````mdx\n```d2\ndirection: right\nweb -> api -> db\n```\n````\n\nThe plugin renders each D2 code block at build time and inlines the SVG.\n\nOption B — **Checked-in SVG**:\n\n```mdx\nimport system from '@site/static/img/architecture/system.svg';\n\n\u003cimg src={system} alt=\"System\" />\n```\n\nSimpler but requires manual regeneration.\n\n---\n\n## Nextra / Next.js\n\nNextra 3 supports MDX with custom code block handling.\n\n`mdx-components.tsx`:\n\n```tsx\nimport { renderD2 } from './lib/d2';\n\nexport function useMDXComponents(components) {\n return {\n ...components,\n pre: ({ children, ...props }) => {\n const codeProps = children?.props;\n if (codeProps?.className === 'language-d2') {\n return \u003cdiv dangerouslySetInnerHTML={{ __html: renderD2(codeProps.children) }} />;\n }\n return \u003cpre {...props}>{children}\u003c/pre>;\n },\n };\n}\n```\n\n`lib/d2.ts` can shell out to the `d2` CLI at build time (via `next.config.js` + a build script) or use `@terrastruct/d2` WASM.\n\n---\n\n## Astro / SvelteKit / Remix\n\nAll support Markdown/MDX with custom renderers. Pattern is identical:\n\n1. Detect `d2` code fences.\n2. Shell out to `d2` binary at build time.\n3. Inline the SVG.\n\nAn alternative: precompile D2 sources to SVGs in a separate pipeline step, commit them, and embed as static assets.\n\n---\n\n## WASM (live render in browser)\n\nD2 ships a WASM build for in-browser rendering:\n\n```sh\nnpm install @terrastruct/d2-wasm\n```\n\n```ts\nimport { D2 } from '@terrastruct/d2-wasm';\n\nconst d2 = new D2();\nconst svg = await d2.compile('web -> api -> db');\ndocument.getElementById('chart').innerHTML = svg;\n```\n\nTrade-offs:\n- Bundle size: WASM + wasm-pack runtime adds ~3 MB.\n- Performance: acceptable for small diagrams; slow on large graphs.\n- Use case: interactive playgrounds, docs where diagrams change based on user selection.\n\nFor most docs, build-time rendering is cheaper and faster.\n\n---\n\n## Obsidian / Notion / VS Code\n\n- **Obsidian** — plugin `obsidian-d2` renders `d2` code blocks in the preview pane.\n- **Notion** — no native support; export SVG and embed as image.\n- **VS Code** — `terrastruct.d2` extension gives syntax highlighting + preview pane (`Cmd+Shift+V`).\n\n---\n\n## CI pipelines\n\n### GitHub Actions\n\n```yaml\nname: Render D2 diagrams\non:\n push:\n paths:\n - 'docs/architecture/*.d2'\n\njobs:\n render:\n runs-on: ubuntu-latest\n steps:\n - uses: actions/checkout@v4\n - name: Install d2\n run: curl -fsSL https://d2lang.com/install.sh | sh -s --\n - name: Render\n run: |\n for f in docs/architecture/*.d2; do\n d2 \"$f\" \"${f%.d2}.svg\"\n d2 --theme=200 \"$f\" \"${f%.d2}-dark.svg\"\n done\n - name: Commit rendered SVGs\n uses: stefanzweifel/git-auto-commit-action@v5\n with:\n commit_message: 'chore: rerender D2 diagrams'\n```\n\n### GitLab CI\n\n```yaml\nrender:d2:\n image: terrastruct/d2\n script:\n - for f in docs/architecture/*.d2; do d2 \"$f\" \"${f%.d2}.svg\"; done\n artifacts:\n paths:\n - docs/architecture/*.svg\n```\n\n### Docker\n\n```dockerfile\nFROM terrastruct/d2 AS renderer\nCOPY docs/architecture /d2\nRUN for f in /d2/*.d2; do d2 \"$f\" \"${f%.d2}.svg\"; done\n\nFROM nginx:alpine\nCOPY --from=renderer /d2/*.svg /usr/share/nginx/html/diagrams/\n```\n\n---\n\n## Confluence / Google Docs / Slack\n\nThese platforms don't support D2 natively. Workflow:\n\n1. Render to SVG or PNG locally.\n2. Drag-and-drop / upload into the platform.\n3. Keep the `.d2` source in git; note its path in the page so future edits find the source.\n\nNever re-draw a D2 diagram in the native tool — you'll duplicate truth and drift.\n\n---\n\n## Project convention\n\nStandard layout for a project that uses D2:\n\n```\ndocs/\n architecture/\n system.d2 # checked in\n system.svg # committed (if using checked-in SVG flow)\n system-dark.svg\n deployment.d2\n deployment.svg\n sequences/\n oauth.d2\n oauth.svg\n erd/\n core-schema.d2\n core-schema.svg\n shared/\n styles.d2 # imported via ...@shared/styles.d2\n icons.d2\n```\n\nTop-level `shared/styles.d2`:\n\n```d2\nvars: {\n d2-config: {\n theme-id: 0\n dark-theme-id: 200\n layout-engine: elk\n pad: 80\n }\n color: {\n frontend: \"#eff6ff\"\n backend: \"#ecfdf5\"\n database: \"#f5f3ff\"\n }\n}\n\nclasses: {\n frontend: {style: {fill: ${color.frontend}; stroke: \"#3b82f6\"}}\n backend: {style: {fill: ${color.backend}; stroke: \"#10b981\"}}\n db: {shape: cylinder; style: {fill: ${color.database}; stroke: \"#8b5cf6\"}}\n}\n```\n\nEvery diagram starts with:\n\n```d2\n...@../shared/styles.d2\n\ndirection: right\n# …the diagram\n```\n\nChange one file → every diagram retints. This is the core payoff of text-based diagrams.\n\n---\n\n## Anti-patterns\n\n- **Committing `.svg` without the `.d2` source** — loses editability; treat the source as canonical.\n- **Re-drawing D2 diagrams in Excalidraw** for a specific doc — duplicate truth.\n- **Live WASM rendering for a static docs site** — adds MB of JS; precompile.\n- **Per-doc theme customization** — drift; use a shared styles file.\n- **No CI render** — PRs ship stale SVGs; the diagram diverges from the source.\n","content_type":"text/markdown; charset=utf-8","language":"markdown","size":7846,"content_sha256":"84555e57b1efa5db8c8881da8dfa3b7c3aeedc3d2bce621b5c5ae4a06994a2fd"},{"filename":"references/layouts.md","content":"# Layouts Reference\n\nD2 chooses where to put nodes; you choose only *direction*, *grouping*, and sometimes a *grid*. This file covers the dials you actually touch and the engine tradeoffs behind them.\n\n---\n\n## Table of Contents\n\n- [Direction](#direction)\n- [Grid layouts](#grid-layouts)\n- [Layout engines](#layout-engines)\n- [Label positioning with `near:`](#label-positioning-with-near)\n- [Label placement with `label.near:` (on containers / shapes)](#label-placement-with-labelnear-on-containers-shapes)\n- [Size control — `width`, `height`](#size-control-width-height)\n- [Spacing knobs (per diagram)](#spacing-knobs-per-diagram)\n- [Pad & center (global canvas)](#pad-center-global-canvas)\n- [Layout recipes](#layout-recipes)\n- [Anti-patterns](#anti-patterns)\n\n\n## Direction\n\nSet once at the root; overridable per container.\n\n```d2\ndirection: down # top-down flow (default on many templates)\n# direction: right # left-to-right (idiomatic for architecture / pipeline)\n# direction: up # bottom-up (rare; e.g., dependency stacks)\n# direction: left # right-to-left (very rare)\n\nweb -> api -> db\n```\n\nPick once, up front — it's the biggest lever on readability.\n\n| Direction | Best for |\n|---|---|\n| `right` | Architecture, request flow, pipelines (reads left→right like text) |\n| `down` | Org charts, trees, decision flows (reads top→bottom) |\n| `up` | Dependency stacks (\"built on top of…\") |\n| `left` | Right-to-left reading order / reversed pipelines |\n\n### Per-container direction\n\nDifferent engines handle mixed directions differently.\n\n```d2\ndirection: down\n\norchestration: {\n direction: right\n queue -> worker -> db\n}\n```\n\n- **dagre** (default): flattens child direction to match root. Mixed directions are effectively ignored.\n- **elk**: honors child direction; you get the \"L-shape\" inner layout.\n- **tala**: honors child direction and optimizes routing around it.\n\nIf mixed-direction is important, switch engine (`layout-engine: elk`).\n\n---\n\n## Grid layouts\n\nLay out children on an explicit grid inside a container — orthogonal to the layout engine.\n\n```d2\nk8s_cluster: Cluster {\n grid-rows: 3\n grid-columns: 4\n grid-gap: 20 # spacing between cells, px\n # optional: horizontal-gap / vertical-gap for asymmetric spacing\n\n pod-a\n pod-b\n pod-c\n # …up to 12 cells\n}\n```\n\nRules:\n\n- Specify **one** of `grid-rows` / `grid-columns` (the engine derives the other) or both (fixed grid).\n- Children are placed in declaration order.\n- Connections between grid cells render as usual — useful for matrix layouts where the *positions* are meaningful.\n- Grid containers ignore the layout engine's internal routing for their children.\n\nUse grid for:\n- k8s node/pod matrices\n- Comparison tables (rows = axes, columns = options)\n- Regular array layouts where semantic position matters\n\n---\n\n## Layout engines\n\nSet once at the top:\n\n```d2\nvars: {\n d2-config: {\n layout-engine: dagre # dagre | elk | tala\n }\n}\n```\n\n| Engine | License | Strengths | Weaknesses |\n|---|---|---|---|\n| **dagre** | MIT, bundled | Fast, predictable DAG layouts, no install | Flattens per-container direction; limited orthogonal routing; cramped on dense graphs |\n| **elk** | EPL, bundled since v0.7 | Hierarchical layouts shine; orthogonal edges; respects per-container direction | Slower on large graphs; occasional long edges |\n| **tala** | Proprietary, separate binary | Best-in-class density, crossings, label placement, compound routing | Paid; must be installed separately (`d2 --layout=tala` after install) |\n\n### Choosing\n\n- **Small-to-medium architecture, flat or shallow nesting** → `dagre`.\n- **Deep nested containers, multiple per-container directions** → `elk`.\n- **Dense diagrams (~50+ nodes, many crossings), client will pay for polish** → `tala`.\n\nTell the user in the file header if non-default:\n\n```d2\n# Requires: d2 + tala (paid) — install: https://d2lang.com/tour/tala\nvars: {\n d2-config.layout-engine: tala\n}\n```\n\n---\n\n## Label positioning with `near:`\n\nShapes can be pinned \"near\" a constant position — effectively a floating label layer.\n\n```d2\ntitle: A winning strategy {\n shape: text\n near: top-center # position on the canvas\n style: {\n font-size: 32\n bold: true\n }\n}\n\nlegend: {\n near: bottom-right\n # …legend body\n}\n```\n\nValues for `near`:\n\n- `top-left`, `top-center`, `top-right`\n- `center-left`, `center-right`\n- `bottom-left`, `bottom-center`, `bottom-right`\n\nCommon uses:\n\n- Diagram title (`near: top-center`).\n- Legend box pinned to a corner.\n- \"Caption\" `text` shape `near: bottom-center`.\n\nThe engine will not route edges through pinned-near shapes — they live on a floating layer.\n\n---\n\n## Label placement with `label.near:` (on containers / shapes)\n\nDifferent from `near:` — this is the label's position *relative to its own shape*.\n\n```d2\naws: AWS {\n label.near: outside-top-left\n # …children\n}\n```\n\nValues: `top-left | top-center | top-right | center-left | center-right | bottom-left | bottom-center | bottom-right | outside-top-left | outside-top-center | outside-top-right | outside-bottom-left | outside-bottom-center | outside-bottom-right`.\n\n`outside-*` is useful for containers whose interior should be fully readable — the label floats just above the frame.\n\n---\n\n## Size control — `width`, `height`\n\n```d2\nbig_node: Aggregator {\n width: 260\n height: 140\n}\n```\n\n- Values are pixels.\n- The layout engine still places the node; you only fix its size.\n- Use sparingly — D2 sizes by label length by default, which usually looks fine.\n\nCommon case: a callout / note shape that needs to feel prominent.\n\n---\n\n## Spacing knobs (per diagram)\n\nThere's no global `node-spacing` in D2 syntax (as of 0.7). Spacing is driven by:\n\n- The chosen layout engine.\n- `grid-gap` / `horizontal-gap` / `vertical-gap` for grid containers.\n- Container nesting (containers force extra padding).\n\nIf a diagram feels cramped:\n1. Shorten labels.\n2. Flatten one level of nesting.\n3. Switch engine to `elk` or `tala`.\n4. Break into multiple `layers:` (see `composition.md`).\n\n---\n\n## Pad & center (global canvas)\n\n```d2\nvars: {\n d2-config: {\n pad: 100 # px of padding around entire diagram\n center: true # center the diagram in the viewport\n }\n}\n```\n\nUseful when the rendered SVG embeds in a doc with a background color that contrasts poorly with a flush-left diagram.\n\n---\n\n## Layout recipes\n\n### Architecture (left → right, grouped)\n\n```d2\ndirection: right\nvars.d2-config.layout-engine: elk\n\nclasses: {\n service: { style.fill: \"#ecfdf5\"; style.stroke: \"#10b981\" }\n db: { shape: cylinder; style.fill: \"#f5f3ff\"; style.stroke: \"#8b5cf6\" }\n}\n\nuser.shape: person\n\nfrontend: {\n web.class: service\n}\n\nbackend: {\n api.class: service\n worker.class: service\n}\n\ndata: {\n primary.class: db\n cache.shape: stored_data\n}\n\nuser -> frontend.web -> backend.api\nbackend.api -> data.primary\nbackend.api -> data.cache\nbackend.api -> backend.worker\n```\n\n### Decision flow (top → bottom)\n\n```d2\ndirection: down\n\nstart.shape: circle\ndecide.shape: diamond\npath_a.shape: rectangle\npath_b.shape: rectangle\nend.shape: oval\n\nstart -> decide\ndecide -> path_a: yes\ndecide -> path_b: no\npath_a -> end\npath_b -> end\n```\n\n### Grid matrix (k8s cluster)\n\n```d2\ndirection: right\n\ncluster: Production Cluster {\n grid-rows: 3\n grid-columns: 4\n grid-gap: 16\n\n node1: { label: \"node-01\\n4 vCPU / 16Gi\" }\n node2: { label: \"node-02\\n4 vCPU / 16Gi\" }\n node3: { label: \"node-03\\n8 vCPU / 32Gi\" }\n # …\n}\n```\n\n---\n\n## Anti-patterns\n\n- **Forcing direction per-container under dagre** — it's ignored; either switch to elk or flatten.\n- **Global `width` / `height` on every node** — fights the layout; pick one \"big\" shape and let D2 size the rest.\n- **Using a grid when you actually want a list** — if position isn't meaningful, the grid forces awkward empty cells.\n- **`near: top-center` on multiple shapes** — they stack awkwardly; use one title plus inline `label` elsewhere.\n- **Swapping engines mid-diagram** — impossible, but users sometimes try via two `vars.d2-config.layout-engine` blocks. Only the last one wins.\n","content_type":"text/markdown; charset=utf-8","language":"markdown","size":8141,"content_sha256":"f012585a74e8996a046ad35aae3c50b1be86b385e666a64580a84e2cdfa316b3"},{"filename":"references/render.md","content":"# Render Reference\n\nWhat to tell the user so they can turn `.d2` source into pixels. This skill never runs the renderer — but it must hand over the right command.\n\n---\n\n## Table of Contents\n\n- [Install D2](#install-d2)\n- [Basic render](#basic-render)\n- [CLI flag cheat sheet](#cli-flag-cheat-sheet)\n- [Watch mode](#watch-mode)\n- [Themes](#themes)\n- [Sketch mode](#sketch-mode)\n- [Layout engines](#layout-engines)\n- [Animated GIFs from `steps:`](#animated-gifs-from-steps)\n- [Rendering multi-board diagrams](#rendering-multi-board-diagrams)\n- [Bundling remote assets](#bundling-remote-assets)\n- [Icon catalogs](#icon-catalogs)\n- [Playground (no install required)](#playground-no-install-required)\n- [Size tips](#size-tips)\n- [Error output](#error-output)\n\n\n## Install D2\n\nCross-platform install options:\n\n```sh\n# macOS (Homebrew)\nbrew install d2\n\n# Linux / macOS (official install script)\ncurl -fsSL https://d2lang.com/install.sh | sh -s --\n\n# Go\ngo install oss.terrastruct.com/d2@latest\n\n# Docker\ndocker pull terrastruct/d2\ndocker run --rm -v \"$(pwd)\":/d2 terrastruct/d2 input.d2 output.svg\n\n# Windows\nscoop install d2\n# or via the install script under WSL\n```\n\nVerify: `d2 --version`.\n\n---\n\n## Basic render\n\n```sh\nd2 input.d2 # writes input.svg alongside input.d2\nd2 input.d2 output.svg # explicit output path\nd2 input.d2 output.png # PNG — requires Chromium (auto-downloaded)\nd2 input.d2 output.pdf # PDF\nd2 input.d2 output.gif # GIF (requires steps:)\n```\n\nOutput format is inferred from the output file extension.\n\n---\n\n## CLI flag cheat sheet\n\n| Flag | Meaning |\n|---|---|\n| `--theme ID` | Light theme ID (default 0) |\n| `--dark-theme ID` | Dark theme ID (pairs with `--theme` for auto light/dark) |\n| `--layout ENGINE` | `dagre` (default) / `elk` / `tala` |\n| `--sketch` | Hand-drawn look |\n| `--pad N` | Canvas padding in px (default 100) |\n| `--center` | Center diagram in viewport |\n| `--watch` | Live-reload browser preview on file change |\n| `--host HOST`, `--port N` | Watch-mode server binding (default localhost:0) |\n| `--bundle` | Inline remote assets (icons) into the SVG |\n| `--scale N` | Render scale (for PNG) |\n| `--animate-interval MS` | GIF frame interval (for `steps:` diagrams) |\n| `--dry-run` | Parse-check without emitting output |\n| `--var KEY=VAL` | Override a `vars.*` entry at render time |\n| `--timeout SEC` | Render timeout |\n| `--target NAME` | Render a specific layer / scenario / step |\n| `--stdout-format FMT` | Write SVG / text to stdout instead of a file |\n\nRun `d2 --help` for the full list; the flags above cover ~95% of usage.\n\n---\n\n## Watch mode\n\nBest for iteration:\n\n```sh\nd2 --watch architecture.d2\n# opens http://localhost:\u003crandom> in a browser; re-renders on save\n```\n\nStops on Ctrl+C. Watch mode also runs a self-contained web server with click-through navigation for `layers:` / `scenarios:` / `steps:` diagrams.\n\n---\n\n## Themes\n\nD2 ships with a gallery of numbered themes.\n\n### Light themes (by ID)\n\n| ID | Name | Vibe |\n|---|---|---|\n| 0 | Neutral default | Clean, professional |\n| 1 | Neutral gray | Understated |\n| 3 | Flagship terrastruct | Signature bluish |\n| 4 | Cool classics | Muted cool palette |\n| 5 | Mixed berry blue | Blue-forward |\n| 6 | Grape soda | Pink/purple pop |\n| 7 | Aubergine | Warm purple |\n| 8 | Colorblind clear | Accessible palette |\n| 100 | Shallow sea | Teal accent |\n| 101 | Dark mauve | Dusky |\n| 102 | Dark flagship | Bold accent |\n\n### Dark themes\n\n| ID | Name |\n|---|---|\n| 200 | Dark mauve |\n| 201 | Dark flagship |\n\nPair a light + dark theme:\n\n```sh\nd2 --theme 0 --dark-theme 200 input.d2 output.svg\n```\n\nThe output SVG embeds both palettes and switches with the user's OS theme (when embedded in HTML that respects `prefers-color-scheme`).\n\nSet in-file via `vars.d2-config.theme-id` and `vars.d2-config.dark-theme-id` — CLI flags override.\n\n---\n\n## Sketch mode\n\n```sh\nd2 --sketch input.d2 output.svg\n```\n\nOr in-file:\n\n```d2\nvars.d2-config.sketch: true\n```\n\nRenders a hand-drawn look. Good for brainstorming / whiteboard-style diagrams; step back to crisp rendering for production docs.\n\n---\n\n## Layout engines\n\n```sh\nd2 --layout=elk input.d2\nd2 --layout=tala input.d2 # requires paid install\n```\n\nSee `layouts.md` for when to pick each. `tala` must be installed separately (https://terrastruct.com/tala).\n\n---\n\n## Animated GIFs from `steps:`\n\n```d2\n# lifecycle.d2\nsteps: {\n s1: { client -> api }\n s2: { api -> db }\n s3: { api -> client }\n}\n```\n\n```sh\nd2 --animate-interval=1500 lifecycle.d2 lifecycle.gif\n```\n\n- `--animate-interval` is milliseconds per frame.\n- 1000–2000ms is typical; too fast = unreadable, too slow = tedious.\n\nSame works for `scenarios:` to produce a walk-through GIF across scenarios.\n\n---\n\n## Rendering multi-board diagrams\n\n`layers:` / `scenarios:` / `steps:` produce multi-board output:\n\n```sh\nd2 main.d2 main.svg # renders all boards with a navigator\nd2 --target='root.*.deployment' main.d2 deployment.svg\n```\n\n`--target` accepts a board path; wildcards supported.\n\n---\n\n## Bundling remote assets\n\nIf the diagram references icons by URL:\n\n```d2\ngithub.icon: https://icons.terrastruct.com/dev/github.svg\n```\n\nRender with inlined icons:\n\n```sh\nd2 --bundle input.d2 output.svg\n```\n\nWithout `--bundle`, the SVG references the icons remotely — broken on offline viewing.\n\n---\n\n## Icon catalogs\n\n| Source | URL | Notes |\n|---|---|---|\n| Terrastruct (official) | https://icons.terrastruct.com/ | AWS / GCP / Azure / K8s / dev / tech |\n| SVG Repo | https://www.svgrepo.com/ | Permissively licensed SVGs |\n| Simple Icons | https://simpleicons.org/ | Brand icons |\n| Material Icons | Material symbols | Permissive, universal |\n\nUsage:\n\n```d2\naws_lambda.icon: https://icons.terrastruct.com/aws/Compute/AWS-Lambda_light-bg.svg\n```\n\nLocal icons:\n\n```d2\nlogo.icon: ./assets/logo.svg # bundled with --bundle\n```\n\n---\n\n## Playground (no install required)\n\n- https://play.d2lang.com/ — paste `.d2` source, live render, shareable URL.\n- https://terrastruct.com/d2 — official landing, links to tour and docs.\n\nUseful for quick sanity checks and for users who won't install D2 locally.\n\n---\n\n## Size tips\n\n- **Large diagrams feel slow in watch mode** — split into `layers:` or `@file` imports; render each separately.\n- **PNG outputs look blurry** — use `--scale 2` or `--scale 4` for 2×/4× raster.\n- **Files grow past 500 lines** — split by imports; a 2000-line `.d2` file is a red flag.\n\n---\n\n## Error output\n\nD2 errors appear on stderr with file:line context:\n\n```\nerr: identifier \"databse\" unknown\n --> input.d2:14:3\n```\n\nPipe through `less` for large files:\n\n```sh\nd2 input.d2 2>&1 | less\n```\n\nBut remember: most D2 \"errors\" are silent shape/style fallbacks — not stderr messages. Always validate visually.\n","content_type":"text/markdown; charset=utf-8","language":"markdown","size":6800,"content_sha256":"c7d7e2d3a994fef11fdaf9101b953fad1439797a3341c01f54c62cafdbf5b4f3"},{"filename":"references/shapes.md","content":"# Shape Catalog\n\nEvery built-in D2 shape, grouped by what it communicates. `shape:` is a property, not a keyword — a typo silently falls back to `rectangle`, so always match this catalog exactly.\n\nAll shapes accept: `label`, `icon`, `tooltip`, `link`, `near`, `width`, `height`, `class`, and any `style.*` key from `styles.md`.\n\n---\n\n## Table of Contents\n\n- [Generic](#generic)\n- [System & data](#system-data)\n- [People](#people)\n- [Text & rich content](#text-rich-content)\n- [Icons on any shape](#icons-on-any-shape)\n- [Shape + class combo](#shape-class-combo)\n- [Shape fallback behavior (gotcha)](#shape-fallback-behavior-gotcha)\n- [Choosing shape by concept](#choosing-shape-by-concept)\n\n\n## Generic\n\n| Shape | ID | Use for |\n|---|---|---|\n| Rectangle | `rectangle` | Default — abstract box, generic component |\n| Square | `square` | Component where the 1:1 aspect ratio matters (icons, modules) |\n| Circle | `circle` | Start / end / event / generic token |\n| Oval | `oval` | Actor in a flowchart, state in a state machine |\n| Diamond | `diamond` | Decision / branching in a flowchart |\n| Hexagon | `hexagon` | Pipeline stage, honeycomb layouts |\n| Parallelogram | `parallelogram` | Input / output step in a flowchart |\n\n```d2\nstart.shape: circle\ndecide.shape: diamond\nload.shape: parallelogram\n```\n\n---\n\n## System & data\n\n| Shape | ID | Use for |\n|---|---|---|\n| Cylinder | `cylinder` | Database, storage volume, block device |\n| Queue | `queue` | Message queue, event bus, stream |\n| Page | `page` | Static document, report |\n| Document | `document` | File, spec, markdown doc |\n| Stored data | `stored_data` | Cache, key-value store — not relational DB |\n| Package | `package` | Library, bundle, npm package |\n| Cloud | `cloud` | External service, SaaS, internet |\n| Step | `step` | Workflow stage, stage gate |\n| Callout | `callout` | Annotation, footnote reference |\n\n```d2\npg.shape: cylinder\npg.label: Postgres 16\nevents.shape: queue\nevents.label: Kafka\ncache.shape: stored_data\ncdn.shape: cloud\n```\n\n**`cylinder` vs `stored_data`** — use `cylinder` for relational / row-oriented databases and disk volumes, `stored_data` for caches, KV stores, object storage.\n\n**`queue` vs generic `rectangle` with \"queue\" label** — the queue shape (an open-ended cylinder) is instantly recognizable; don't waste it.\n\n---\n\n## People\n\n| Shape | ID | Use for |\n|---|---|---|\n| Person | `person` | Human user, operator, developer |\n\n```d2\nuser.shape: person\nuser.label: Shopper\n```\n\nUse exactly one \"person\" per user role. Multiple people on a diagram should have distinct labels — `visitor`, `admin`, `operator`.\n\n---\n\n## Text & rich content\n\n| Shape | ID | Use for |\n|---|---|---|\n| Text | `text` | Title, caption, legend — no border by default |\n| Code | `code` | Code snippet (markdown fenced) |\n| Class | `class` | UML class box with fields / methods (special syntax) |\n| Sql table | `sql_table` | ERD table with typed columns (special syntax) |\n| Image | `image` | Pure icon, no label — just the image |\n| Sequence diagram | `sequence_diagram` | Container whose children are actors on a lifeline |\n\nClass, sql_table, and sequence_diagram have dedicated syntax — see `special-diagrams.md`.\n\n---\n\n### `text` — titles, labels-as-shapes, freeform notes\n\n```d2\ntitle: A winning strategy {\n shape: text\n near: top-center\n style.font-size: 36\n style.bold: true\n}\n```\n\n`text` has no border or fill by default. For a labeled block with rendered markdown content, use a normal rectangle with a `|md ... |` label instead.\n\n---\n\n### `code` — inline code snippet\n\n```d2\nhandler: {\n shape: code\n language: go\n content: |go\n func Handle(w http.ResponseWriter, r *http.Request) {\n fmt.Fprintln(w, \"ok\")\n }\n |\n}\n```\n\nUse for \"this is the exact snippet that does X\" — evidence artifacts in architecture diagrams.\n\n---\n\n### `image` — icon as a standalone shape\n\n```d2\ndirection: right\nserver: {\n shape: image\n icon: https://icons.terrastruct.com/tech/022-server.svg\n}\ngithub: {\n shape: image\n icon: https://icons.terrastruct.com/dev/github.svg\n}\nserver -> github\n```\n\nDifference from `icon:` on a regular shape: `shape: image` is **only** the icon with no enclosing box and no label rendered below. Use for decorative elements where the icon alone communicates.\n\n---\n\n## Icons on any shape\n\nAny shape can carry an `icon:`, rendered inside the shape alongside its label.\n\n```d2\napi: {\n icon: https://icons.terrastruct.com/dev/nodejs.svg\n label: API Server\n}\n```\n\nIcon sources:\n\n- \u003chttps://icons.terrastruct.com/> — the official catalog (AWS / GCP / Azure / K8s / dev / tech)\n- \u003chttps://www.svgrepo.com/> — permissively-licensed SVGs\n- Local file paths (`icon: ./assets/logo.svg`) — bundled with `d2 --bundle`\n\n---\n\n## Shape + class combo\n\nThe common pattern is a class that encodes both the shape and the style:\n\n```d2\nclasses: {\n db: {\n shape: cylinder\n style.fill: \"#f5f3ff\"\n style.stroke: \"#8b5cf6\"\n }\n}\n\nusers.class: db\norders.class: db\npayments.class: db\n```\n\nOne-line per database instead of 3-line maps each. See `classes.md`.\n\n---\n\n## Shape fallback behavior (gotcha)\n\nD2 will silently render `shape: databse` (typo) as a rectangle. There's no warning. **Always copy `shape:` values from this catalog** and prefer classes (`.class: db`) over re-typing `shape: cylinder` on every database node — a typo in a class fires once; a typo on each node fires repeatedly.\n\n---\n\n## Choosing shape by concept\n\n| Concept | Shape |\n|---|---|\n| Service / microservice | `rectangle` (default) |\n| Database (relational) | `cylinder` |\n| Cache / KV store | `stored_data` |\n| Queue / event bus | `queue` |\n| External SaaS / third-party | `cloud` |\n| Human user | `person` |\n| Decision point in a flow | `diamond` |\n| Stage in a pipeline | `step` or `hexagon` |\n| Document / spec | `document` or `page` |\n| Start / end state | `circle` or `oval` |\n| Title / caption | `text` |\n| ERD table | `sql_table` |\n| OO class | `class` |\n| Pure icon | `image` |\n| Sequence lifeline container | `sequence_diagram` |\n\nWhen none fit, default to `rectangle` with an informative label — not an invented shape name.\n","content_type":"text/markdown; charset=utf-8","language":"markdown","size":6140,"content_sha256":"b60693e2361c830458f8858db0aeb7a727aaf520f2d963cdc58f299b1fdd63b2"},{"filename":"references/special-diagrams.md","content":"# Special Diagrams Reference\n\nFour D2 shape types have dedicated syntax and rendering: `sequence_diagram`, `sql_table`, `class`, and markdown/code/LaTeX blocks. Their rules differ from ordinary shapes — learn them once, reuse them.\n\n---\n\n## Table of Contents\n\n- [Sequence diagrams — `shape: sequence_diagram`](#sequence-diagrams-shape-sequencediagram)\n- [ERDs — `shape: sql_table`](#erds-shape-sqltable)\n- [UML class — `shape: class`](#uml-class-shape-class)\n- [Markdown inside shapes](#markdown-inside-shapes)\n- [Code blocks inside shapes](#code-blocks-inside-shapes)\n- [LaTeX blocks](#latex-blocks)\n- [Image shapes](#image-shapes)\n- [Choosing among special shapes](#choosing-among-special-shapes)\n- [Anti-patterns](#anti-patterns)\n\n\n## Sequence diagrams — `shape: sequence_diagram`\n\nContainer-level shape. Children become actors (lifelines); messages between them become spans.\n\n```d2\noauth: OAuth 2.0 Authorization Code Flow {\n shape: sequence_diagram\n\n user: User\n browser: Browser\n client: Client App\n auth: Auth Server\n api: Resource API\n\n user -> browser: click \"Sign in\"\n browser -> client: GET /login\n client -> auth: redirect to /authorize\n auth -> browser: consent screen\n browser -> auth: POST /consent\n auth -> client: redirect w/ code\n client -> auth: POST /token (code)\n auth -> client: access_token + refresh_token\n client -> api: GET /me (Bearer)\n api -> client: 200 { profile }\n client -> browser: render dashboard\n}\n```\n\nRules:\n- Actors render in declaration order, left-to-right.\n- Each `a -> b: message` adds a message span.\n- Chains (`a -> b -> c: ...`) become sequential messages.\n\n### Self-messages\n\n```d2\napi -> api: validate token\n```\n\nRender as a loop-back arrow on the same actor.\n\n### Groups (nested maps as groups)\n\n```d2\noauth: {\n shape: sequence_diagram\n user; browser; client; auth\n\n auth_flow: Authorization phase {\n user -> browser -> client -> auth: initiate\n auth -> client: code\n }\n\n token_exchange: Token phase {\n client -> auth: POST /token\n auth -> client: access_token\n }\n}\n```\n\nEach nested map becomes a labeled group box around its messages.\n\n### Notes\n\n```d2\noauth: {\n shape: sequence_diagram\n client; auth\n\n client -> auth: POST /token\n note_over_auth: auth {\n shape: text\n label: \"Validates code,\\nissues JWT\"\n }\n auth -> client: access_token\n}\n```\n\nNotes in D2 are just `text` shapes placed between messages — declaration order controls their vertical position.\n\n### Ordering guarantees\n\nMessages and groups are rendered **top-to-bottom in source order**. This makes sequence diagrams trivially diffable — reordering lines reorders the diagram.\n\n### When to use sequence diagrams\n\n- Protocol exchanges (OAuth, TLS handshake, distributed consensus rounds).\n- API call flows across services.\n- Timing-sensitive interactions where \"before\" vs \"after\" matters.\n\nWhen **not** to:\n- \"Who calls whom\" without ordering — use an architecture diagram.\n- More than ~7 actors — becomes unreadable.\n\n---\n\n## ERDs — `shape: sql_table`\n\nRelational tables with typed columns and foreign keys.\n\n```d2\nusers: {\n shape: sql_table\n id: int {constraint: primary_key}\n email: varchar(255) {constraint: unique}\n created_at: timestamp\n}\n\norders: {\n shape: sql_table\n id: int {constraint: primary_key}\n user_id: int {constraint: foreign_key}\n total: decimal(10,2)\n status: varchar(32)\n}\n\norders.user_id -> users.id: fk\n```\n\n### Column syntax\n\nInside a `sql_table`, each line is `column_name: type`. Types are free-form text — use whatever your DDL uses (`uuid`, `jsonb`, `text`, `int4`, etc.).\n\n### Constraints\n\n```d2\norders: {\n shape: sql_table\n id: int {constraint: primary_key}\n user_id: int {constraint: foreign_key}\n sku: text {constraint: unique}\n email: text {constraint: [unique; not_null]} # array for multiple\n}\n```\n\nBuilt-in constraint strings: `primary_key`, `foreign_key`, `unique`, `not_null`. Other strings render as plain labels on the column.\n\n### Foreign-key arrows — crow's foot idiom\n\n```d2\norders.user_id -> users.id: {\n source-arrowhead: {shape: cf-many; label: \"*\"}\n target-arrowhead: {shape: cf-one-required; label: \"1\"}\n}\n```\n\nCrow's-foot arrowheads (`cf-one`, `cf-one-required`, `cf-many`, `cf-many-required`) encode cardinality. Pair with labels `1`, `*`, `0..1` for clarity.\n\nA class makes this reusable:\n\n```d2\nclasses: {\n fk: {\n source-arrowhead: {shape: cf-many}\n target-arrowhead: {shape: cf-one-required}\n }\n}\n\n(orders.user_id -> users.id)[0].class: fk\n(orders.sku -> products.sku)[0].class: fk\n```\n\n### Styling tables\n\n`sql_table` bodies are fixed-white by design; only the **header band** recolors via `style.stroke`.\n\n```d2\nusers: {\n shape: sql_table\n style.stroke: \"#8b5cf6\" # purple header\n # columns…\n}\n```\n\nUse the same stroke per logical \"table group\" (all user-tables purple, all order-tables orange).\n\n### ERDs vs plain diagrams with DB shapes\n\n- **ERD** = you want to see columns and foreign-key cardinality. Use `sql_table`.\n- **High-level data flow** = you just want \"there's a users DB\". Use `shape: cylinder`.\n\n---\n\n## UML class — `shape: class`\n\n```d2\nVehicle: {\n shape: class\n\n # fields (public by default)\n vin: string\n make: string\n model: string\n\n # methods — identified by parens\n start(): void\n stop(): void\n serialize(): json\n}\n\nCar: {\n shape: class\n # private / protected via prefix\n \\#owner: string # # prefix for private\n \\+insured: bool # + prefix for public (explicit)\n drive(dest: string): void\n}\n\nCar -> Vehicle: extends\n```\n\n### Visibility prefixes\n\nEscape the special chars with `\\`:\n\n| Prefix | Visibility |\n|---|---|\n| `\\+` | public |\n| `\\#` | protected |\n| `\\-` | private |\n| `\\~` | package |\n\n### Relationships\n\nUse arrowheads to encode UML semantics:\n\n| Arrowhead | Meaning |\n|---|---|\n| `triangle` (hollow) | Inheritance — `target-arrowhead.shape: triangle` + `style.filled: false` |\n| `diamond` (hollow) | Aggregation |\n| `diamond` (filled) | Composition |\n| `arrow` | Association / dependency |\n\n```d2\nCar -> Vehicle: {\n target-arrowhead.shape: triangle\n target-arrowhead.style.filled: false\n}\n```\n\n### Styling\n\nSame as `sql_table` — body white, header band colorable via `style.stroke`.\n\n### When to pick `class` vs `sql_table`\n\n- **`class`** — OO design, interfaces, types, method signatures.\n- **`sql_table`** — relational columns, foreign keys, database schemas.\n\nThey look similar but communicate different things.\n\n---\n\n## Markdown inside shapes\n\nAny label can be markdown:\n\n```d2\nnote: {\n label: |md\n ## Key decisions\n\n 1. Migrate to Postgres 16.\n 2. Drop per-tenant schemas.\n 3. Publish change in **#eng-announce**.\n |\n}\n```\n\nThe `|md ... |` fence switches the label to rendered markdown. Works in node labels, container labels, and edge labels.\n\n### Escaping `|`\n\nIf your markdown contains a literal `|`, use a longer fence: `||md ... ||` or `|||md ... |||`.\n\n---\n\n## Code blocks inside shapes\n\n```d2\nhandler: {\n shape: code\n language: go\n content: |go\n func Handle(w http.ResponseWriter, r *http.Request) {\n fmt.Fprintln(w, \"ok\")\n }\n |\n}\n```\n\n- `shape: code` renders the content as a monospace block with syntax highlighting.\n- `language:` is optional; common values: `go`, `python`, `ts`, `js`, `rust`, `sql`, `bash`.\n- Use sparingly — code shapes are tall. Prefer linking out for anything longer than ~15 lines.\n\n---\n\n## LaTeX blocks\n\n```d2\nequation: {\n shape: text\n label: |latex\n E = mc^2\n |\n}\n```\n\nD2 renders LaTeX via KaTeX. Good for papers / academic diagrams; overkill elsewhere.\n\n---\n\n## Image shapes\n\nPure icons with no surrounding box:\n\n```d2\ngithub: {\n shape: image\n icon: https://icons.terrastruct.com/dev/github.svg\n}\n\naws_lambda: {\n shape: image\n icon: https://icons.terrastruct.com/aws/Compute/AWS-Lambda_light-bg.svg\n}\n```\n\nThe `icon:` URL can be remote or a local path. For offline rendering, bundle with `d2 --bundle name.d2 name.svg` — D2 inlines referenced images.\n\n---\n\n## Choosing among special shapes\n\n| If you want to show | Use |\n|---|---|\n| \"A calls B, then B calls C\" with time | `sequence_diagram` |\n| \"Table X has columns and FK to Y\" | `sql_table` |\n| \"Class X extends Y, implements Z\" | `class` |\n| \"Here's the exact code this component runs\" | `shape: code` |\n| \"Here's a rendered doc / note\" | `|md ... |` label |\n| \"Here's an equation\" | `|latex ... |` label |\n| \"Just the logo, no box\" | `shape: image` |\n\n---\n\n## Anti-patterns\n\n- **Mixing `sequence_diagram` and regular nodes** in the same container — D2 treats the whole container as sequence; loose nodes break layout.\n- **`sql_table` with 30 columns** — unreadable; split into logical sub-tables or hide non-key columns.\n- **`class` shapes for data-only entities** — use `sql_table` instead.\n- **Long markdown (>10 lines) in a node** — renders tall and squishes the rest of the diagram. Move to a separate note doc and link via `link:`.\n- **LaTeX in every label** — performance cliff; reserve for actual math.\n","content_type":"text/markdown; charset=utf-8","language":"markdown","size":9014,"content_sha256":"b7bb236c0124a96ea90754039da8d49d69f6f80e7ca1ae8b12fffe96556a011d"},{"filename":"references/styles.md","content":"# Style Reference\n\nEvery supported `style.*` key with the values that matter. Styles apply to shapes, containers, and (with a few exceptions) connections.\n\nAll keys live under `style:` and can be written flat (`x.style.fill: red`) or grouped in a map.\n\n---\n\n## Table of Contents\n\n- [Colors — `fill`, `stroke`](#colors-fill-stroke)\n- [Borders — `stroke`, `stroke-width`, `stroke-dash`, `double-border`](#borders-stroke-stroke-width-stroke-dash-double-border)\n- [Fill pattern — `fill-pattern`](#fill-pattern-fill-pattern)\n- [Shadow, 3D, multiple](#shadow-3d-multiple)\n- [Corners — `border-radius`](#corners-border-radius)\n- [Opacity](#opacity)\n- [Text — `font-size`, `bold`, `italic`, `underline`, `font-color`](#text-font-size-bold-italic-underline-font-color)\n- [Animations — `animated`](#animations-animated)\n- [Text wrap — `text-transform`](#text-wrap-text-transform)\n- [Connection-specific notes](#connection-specific-notes)\n- [Label label-specific (`.label.near`, `.label.style`)](#label-label-specific-labelnear-labelstyle)\n- [Recommended palette](#recommended-palette)\n- [Style precedence (reminder)](#style-precedence-reminder)\n- [Don'ts](#donts)\n\n\n## Colors — `fill`, `stroke`\n\n```d2\nx.style.fill: \"#f5f3ff\" # hex — quote if it contains #\ny.style.fill: lavender # CSS color name\nz.style.fill: \"linear-gradient(0deg, #10b981, #06b6d4)\" # gradient\n```\n\n- Every CSS color name works unquoted.\n- Hex values **must** be quoted (the `#` otherwise starts a comment).\n- Gradients take standard CSS gradient syntax.\n\nFor connections, `stroke` is the line color. For shapes, `stroke` is the border; for `sql_table` and `class`, `stroke` is actually the header-band color because their bodies are fixed-white.\n\n---\n\n## Borders — `stroke`, `stroke-width`, `stroke-dash`, `double-border`\n\n```d2\nx.style.stroke: \"#334155\"\nx.style.stroke-width: 2 # pixels, default 2\nx.style.stroke-dash: 4 # 0 = solid; typical values 2, 4, 6\nx.style.double-border: true # second border ring for emphasis\n```\n\nUse `stroke-dash` to encode \"weak / async / deprecated\". `double-border: true` is an accent — reserve for 1–2 nodes per diagram.\n\n---\n\n## Fill pattern — `fill-pattern`\n\n```d2\naws.style.fill-pattern: dots\naws.style.fill: \"#fff7ed\" # pattern needs a base fill to contrast\n```\n\nValues: `dots`, `lines`, `grid`, `paper`. Useful to distinguish multiple containers without relying on color. Falls back gracefully in older renderers.\n\n---\n\n## Shadow, 3D, multiple\n\n```d2\nx.style.shadow: true # subtle drop shadow\nx.style.3d: true # 3-D extruded look for rectangles\nx.style.multiple: true # stacked appearance (instances, replicas)\n```\n\n- `shadow` adds weight without changing shape; good for \"important\" nodes.\n- `3d` only works on `rectangle`, `square`, `hexagon`. Use for compute / VMs.\n- `multiple` adds a back copy behind the shape — idiomatic for pods / workers / shards / replicas.\n\n```d2\nworkers: Worker pool {\n shape: rectangle\n style.multiple: true\n}\n```\n\n---\n\n## Corners — `border-radius`\n\n```d2\nx.style.border-radius: 8 # rounded corners; 0 = sharp; 999 = pill\n```\n\nShape-dependent: no effect on `cylinder`, `cloud`, etc.\n\n---\n\n## Opacity\n\n```d2\nlegacy.style.opacity: 0.4\n```\n\nValues 0–1. Use for deprecated / future / backgrounded elements so they stay visible but secondary.\n\n---\n\n## Text — `font-size`, `bold`, `italic`, `underline`, `font-color`\n\n```d2\ntitle: {\n shape: text\n style: {\n font-size: 32\n bold: true\n font-color: \"#0f172a\"\n }\n}\n```\n\n- `bold`, `italic`, `underline`: boolean. Default for shape labels is `bold: true`; set to `false` for normal weight.\n- `font-size`: integer, unitless (px).\n- `font-color` overrides the default label color (usually derived from `stroke` / theme).\n\nOn shapes, these style the label. On connections, they style the edge label.\n\n---\n\n## Animations — `animated`\n\n```d2\nkafka.style.animated: true # pulsing border on a shape\n(producer -> kafka)[0].style.animated: true # marching ants on an edge\n```\n\nAnimation is an SVG `\u003canimate>` element — works in browsers, not in static PDF output. Use for event streams and the \"hot path\" in an incident diagram.\n\n---\n\n## Text wrap — `text-transform`\n\n```d2\nbanner.style.text-transform: uppercase\n```\n\nValues: `uppercase`, `lowercase`, `capitalize`, `none`.\n\n---\n\n## Connection-specific notes\n\nConnections share the catalog above but:\n\n- `fill` on a connection styles the **label background**, not the line.\n- `stroke-width` defaults to 2; raise for \"data flow\" emphasis.\n- No `shadow`, `3d`, `multiple`, `border-radius`, `double-border`, `fill-pattern` — silently ignored.\n\n---\n\n## Label label-specific (`.label.near`, `.label.style`)\n\nLabels are themselves stylable objects.\n\n```d2\nwarehouse: Warehouse {\n label.near: outside-top-left\n label.style.font-size: 14\n}\n```\n\n`label.near` values: `top-left | top-center | top-right | center-left | center-right | bottom-left | bottom-center | bottom-right | outside-top-left | outside-top-center | outside-top-right | outside-bottom-...`. For a full-bleed container with the label tucked outside, `outside-top-left` keeps the inside clean.\n\n---\n\n## Recommended palette\n\nConsistent, colorblind-safe defaults that pair with both light and dark themes:\n\n| Semantics | fill | stroke |\n|---|---|---|\n| Frontend | `#eff6ff` | `#3b82f6` |\n| Backend | `#ecfdf5` | `#10b981` |\n| Database | `#f5f3ff` | `#8b5cf6` |\n| Cache | `#fef2f2` | `#ef4444` |\n| Queue | `#fefce8` | `#eab308` |\n| Orchestrator | `#ecfeff` | `#06b6d4` |\n| External / cloud | `#f1f5f9` | `#64748b` |\n| User | `#f0fdf4` | `#22c55e` |\n| Error / failure | `#fee2e2` | `#dc2626` |\n| Deprecated | any with `opacity: 0.4` | — |\n\nConvert these into a class block (see `classes.md`) at the top of the file; the rest of the diagram stays palette-free.\n\n---\n\n## Style precedence (reminder)\n\n1. Theme defaults (from `vars.d2-config.theme-id`).\n2. Global globs (`**.style.fill: ...`).\n3. Class applied by `.class:`.\n4. Direct per-node / per-edge style.\n\nEach layer can override the previous; the last write wins when there's a tie.\n\n---\n\n## Don'ts\n\n- Don't set a gradient on a small shape — it reads as a gradient bug, not an intentional choice.\n- Don't combine `animated` + `stroke-dash` on every edge; the diagram becomes seizure-inducing.\n- Don't use `3d` for anything that isn't a compute / VM instance — it's load-bearing semantics.\n- Don't invent keys (`style.color`, `style.thickness`) — D2 silently ignores unknown keys.\n","content_type":"text/markdown; charset=utf-8","language":"markdown","size":6551,"content_sha256":"fdda2e350a3e4c2ca3baa29c364d5bbf2204c780309b100560cac4dfad4f696f"},{"filename":"references/syntax.md","content":"# Core Syntax Reference\n\nThe minimum grammar needed to read and write D2 without surprises.\n\n---\n\n## Table of Contents\n\n- [Identifiers & Labels](#identifiers-labels)\n- [Maps (attribute blocks)](#maps-attribute-blocks)\n- [Comments](#comments)\n- [Nesting (container declaration)](#nesting-container-declaration)\n- [Connections](#connections)\n- [Style shorthand](#style-shorthand)\n- [Escaping labels](#escaping-labels)\n- [Variables (`vars:`)](#variables-vars)\n- [Globs](#globs)\n- [Order matters (for duplicate keys)](#order-matters-for-duplicate-keys)\n- [Whitespace & indentation](#whitespace-indentation)\n- [Minimum viable file](#minimum-viable-file)\n\n\n## Identifiers & Labels\n\nA line of the form `key` creates a shape whose ID equals its label.\n\n```d2\napi # ID \"api\", label \"api\"\nweb server # ID \"web server\" (spaces OK), label \"web server\"\n```\n\nA line of the form `key: label` sets an explicit label (any unicode string).\n\n```d2\napi: API Gateway\napi.svc: \"gRPC (v1)\" # quotes let you include special chars\n```\n\n**Case is significant.** `API` and `api` are different IDs. Convention: lowercase with spaces for node IDs unless the label must be mixed-case.\n\n**Reserved characters in bare IDs**: `{ } [ ] ( ) : ; # -> \u003c- \u003c-> -- / . & | *`. If any appear in a natural name, wrap the ID in quotes: `\"v1.api\".shape: cylinder`.\n\n**Unicode is fine**. So is `\\n` inside quoted strings to line-break labels: `svc: \"write\\nreplica\"`.\n\n---\n\n## Maps (attribute blocks)\n\nCurly braces attach attributes to a key.\n\n```d2\ndb: {\n shape: cylinder\n label: Primary DB\n style.fill: \"#f5f3ff\"\n}\n```\n\nEquivalent flat form:\n\n```d2\ndb.shape: cylinder\ndb.label: Primary DB\ndb.style.fill: \"#f5f3ff\"\n```\n\nUse the flat form for one-line tweaks, the map form for ≥ 3 attributes on one node. Mix freely in the same file.\n\n---\n\n## Comments\n\n```d2\n# Single-line comment\n\"\"\" block comment\nspanning multiple lines\n\"\"\"\n```\n\nComments belong on decisions, not descriptions. Don't narrate what the next line obviously does.\n\n---\n\n## Nesting (container declaration)\n\nA map that contains other shape declarations is a **container**.\n\n```d2\naws: {\n api: API Gateway\n db: { shape: cylinder }\n api -> db\n}\n```\n\nThe container itself renders as a bordered box grouping its children. See `containers.md` for cross-container references and `_` (parent) syntax.\n\n---\n\n## Connections\n\nFour operators; see `connections.md` for full details.\n\n```d2\na -> b # directed arrow\na \u003c- b # reversed\na \u003c-> b # bidirectional\na -- b # line, no arrows\n```\n\nChained form — one label applies to every segment:\n\n```d2\nfrontend -> api -> db: reads\n```\n\nConnections implicitly declare their endpoints. Writing `a -> b` with no prior `a` or `b` creates both as default rectangles. Declare them first when you need a non-default shape.\n\n---\n\n## Style shorthand\n\n`style.fill: \"#eee\"` on a line of its own equals a map entry:\n\n```d2\n# flat\nx.style.fill: \"#eee\"\n\n# map\nx: { style.fill: \"#eee\" }\n\n# deep map\nx: {\n style: {\n fill: \"#eee\"\n stroke: \"#333\"\n }\n}\n```\n\nPick the form that minimizes repetition. Flat is better for scattered tweaks; map is better for clusters of style keys on one node.\n\n---\n\n## Escaping labels\n\n- **Colon in a label**: quote the whole label — `svc: \"url: /v1\"`.\n- **Brace in a label**: quote it — `cmd: \"{ jq . }\"`.\n- **Markdown / HTML**: wrap with `|md ... |` block (see `special-diagrams.md`).\n- **Literal backslash**: `\"\\\\n\"` for the two chars `\\n`, `\"\\n\"` for a newline.\n\n---\n\n## Variables (`vars:`)\n\nDeclared once at the top of a file and interpolated with `${name}`.\n\n```d2\nvars: {\n primary: \"#0ea5e9\"\n muted: \"#64748b\"\n}\n\nx.style.fill: ${primary}\ny.style.fill: ${muted}\n```\n\nSpecial key `vars.d2-config` is read by the D2 compiler for theme / layout settings. See `vars.md`.\n\n---\n\n## Globs\n\nPattern match across the diagram.\n\n```d2\n*.shape: circle # every top-level shape\n**.style.fill: lightyellow # every shape at any depth\n***.shape: oval # every shape, even in layers / imports\nservice*.class: svc # every top-level shape whose ID starts with \"service\"\n```\n\nUse globs for classes and for defensive styling (\"all databases get a stroke color\"). Don't use them to hide complex per-node styling — name-based rules outlast their authors longer than glob rules do.\n\nSee `classes.md` for the common \"glob + class\" pattern.\n\n---\n\n## Order matters (for duplicate keys)\n\nLater declarations win.\n\n```d2\nx.style.fill: red\nx.style.fill: blue # x ends up blue\n```\n\nGlobs and class applications follow the same \"last write wins\" rule, which is why you put container-specific styling after a global glob.\n\n---\n\n## Whitespace & indentation\n\n- 2-space indent inside maps. The compiler doesn't care; readers do.\n- One blank line between top-level blocks.\n- No trailing whitespace; file ends with `\\n`.\n\nA consistent file diffs cleanly and is grep-friendly — a core reason to use D2 over a binary diagram format in the first place.\n\n---\n\n## Minimum viable file\n\n```d2\ndirection: right\nuser -> web -> api -> db\ndb.shape: cylinder\n```\n\nFour lines, renders to a usable architecture diagram. Start here, add structure as the concept demands.\n","content_type":"text/markdown; charset=utf-8","language":"markdown","size":5193,"content_sha256":"e59baa047b9a44387a679abcd34f9f394b71665335e8d1982ebe885dc285baee"},{"filename":"references/templates.md","content":"# Templates\n\nCopy-paste-ready `.d2` files. Each template is complete and renders as-is with `d2 name.d2 name.svg`. Swap labels and colors; don't rewrite from scratch.\n\n---\n\n## Table of Contents\n\n- [1. Three-tier web architecture](#1-three-tier-web-architecture)\n- [2. Event-driven microservices](#2-event-driven-microservices)\n- [3. Data pipeline](#3-data-pipeline)\n- [4. CI/CD pipeline](#4-cicd-pipeline)\n- [5. OAuth 2.0 sequence](#5-oauth-20-sequence)\n- [6. ERD — e-commerce core](#6-erd-e-commerce-core)\n- [7. Kubernetes cluster grid](#7-kubernetes-cluster-grid)\n- [8. Decision flowchart (incident triage)](#8-decision-flowchart-incident-triage)\n- [9. Multi-view composition (layers)](#9-multi-view-composition-layers)\n- [10. Animated request lifecycle (steps)](#10-animated-request-lifecycle-steps)\n- [Using templates](#using-templates)\n\n\n## 1. Three-tier web architecture\n\n```d2\nvars: {\n d2-config: {\n theme-id: 0\n dark-theme-id: 200\n pad: 80\n layout-engine: elk\n }\n}\n\nclasses: {\n frontend: {style: {fill: \"#eff6ff\"; stroke: \"#3b82f6\"}}\n backend: {style: {fill: \"#ecfdf5\"; stroke: \"#10b981\"}}\n db: {shape: cylinder; style: {fill: \"#f5f3ff\"; stroke: \"#8b5cf6\"}}\n cache: {shape: stored_data; style: {fill: \"#fef2f2\"; stroke: \"#ef4444\"}}\n async: {style: {stroke: \"#06b6d4\"; stroke-dash: 4; animated: true}}\n}\n\ndirection: right\n\nuser: Shopper {\n shape: person\n}\n\nedge: CloudFront + WAF {\n shape: cloud\n style: {fill: \"#f1f5f9\"; stroke: \"#64748b\"}\n}\n\napp: Application tier {\n web.class: frontend\n api.class: backend\n worker.class: backend\n}\n\ndata: Data tier {\n primary: \"PostgreSQL 16\" {class: db}\n replica: \"Read replica\" {class: db; style.opacity: 0.85}\n cache: \"Redis\" {class: cache}\n}\n\nuser -> edge -> app.web -> app.api\napp.api -> data.primary: writes\napp.api -> data.replica: reads\napp.api -> data.cache\n(app.api -> app.worker)[0].class: async\napp.worker -> data.primary\n```\n\n---\n\n## 2. Event-driven microservices\n\n```d2\nvars.d2-config: {\n theme-id: 0\n pad: 80\n layout-engine: elk\n}\n\nclasses: {\n service: {style: {fill: \"#ecfdf5\"; stroke: \"#10b981\"}}\n queue: {shape: queue; style: {fill: \"#fefce8\"; stroke: \"#eab308\"}}\n db: {shape: cylinder; style: {fill: \"#f5f3ff\"; stroke: \"#8b5cf6\"}}\n external: {shape: cloud; style: {fill: \"#f1f5f9\"; stroke: \"#64748b\"}}\n async: {style: {stroke: \"#06b6d4\"; stroke-dash: 4; animated: true}}\n sync: {style.stroke: \"#64748b\"}\n error: {style: {stroke: \"#dc2626\"; stroke-dash: 2}}\n}\n\ndirection: right\n\napi.class: service\norders.class: service\npayments.class: service\nnotifier.class: service\n\nevents: \"Kafka\" {class: queue}\ndlq: \"DLQ\" {class: queue; style.fill: \"#fee2e2\"}\norders_db.class: db\npayments_db.class: db\n\nstripe.class: external\n\n# Sync entry\n(api -> orders)[0].class: sync\n\n# Async fanout\n(orders -> events)[0].class: async\n(events -> payments)[0].class: async\n(events -> notifier)[0].class: async\n\n# Payments\n(payments -> stripe)[0].class: sync\npayments -> payments_db\n\n# Orders\norders -> orders_db\n\n# Failures\n(payments -> dlq)[0].class: error\n```\n\n---\n\n## 3. Data pipeline\n\n```d2\nvars.d2-config: {\n theme-id: 0\n pad: 80\n layout-engine: elk\n}\n\nclasses: {\n source: {shape: cloud; style: {fill: \"#f1f5f9\"; stroke: \"#64748b\"}}\n stage: {shape: step; style: {fill: \"#ecfdf5\"; stroke: \"#10b981\"}}\n sink: {shape: cylinder; style: {fill: \"#f5f3ff\"; stroke: \"#8b5cf6\"}}\n stream: {style: {stroke: \"#06b6d4\"; stroke-dash: 4; animated: true}}\n}\n\ndirection: right\n\ningest: Ingest {\n kafka.class: source\n s3_raw.class: source\n}\n\ntransform: Transform {\n extract.class: stage\n clean.class: stage\n enrich.class: stage\n}\n\nserve: Serve {\n warehouse.class: sink\n feature_store.class: sink\n dashboards: BI dashboards {\n shape: document\n }\n}\n\n(ingest.kafka -> transform.extract)[0].class: stream\n(ingest.s3_raw -> transform.extract)[0].class: stream\ntransform.extract -> transform.clean -> transform.enrich\ntransform.enrich -> serve.warehouse\ntransform.enrich -> serve.feature_store\nserve.warehouse -> serve.dashboards\n```\n\n---\n\n## 4. CI/CD pipeline\n\n```d2\nvars.d2-config: {\n theme-id: 0\n pad: 80\n}\n\nclasses: {\n gate: {shape: diamond; style: {fill: \"#fefce8\"; stroke: \"#eab308\"}}\n job: {shape: step; style: {fill: \"#ecfdf5\"; stroke: \"#10b981\"}}\n art: {shape: package; style: {fill: \"#f5f3ff\"; stroke: \"#8b5cf6\"}}\n env: {shape: cloud; style: {fill: \"#f1f5f9\"; stroke: \"#64748b\"}}\n deploy: {style: {stroke: \"#3b82f6\"; stroke-width: 3}}\n}\n\ndirection: right\n\npush: git push {\n shape: circle\n}\n\nci: CI {\n lint.class: job\n test.class: job\n build.class: job\n image: \"OCI image\" {class: art}\n}\n\nreview: PR review {class: gate}\nmerge: Merge to main {class: gate}\n\ncd: CD {\n staging.class: env\n canary.class: env\n prod.class: env\n}\n\npush -> ci.lint -> ci.test -> ci.build -> ci.image\nci.image -> review -> merge\n(merge -> cd.staging)[0].class: deploy\n(cd.staging -> cd.canary)[0].class: deploy\n(cd.canary -> cd.prod)[0].class: deploy\n```\n\n---\n\n## 5. OAuth 2.0 sequence\n\n```d2\nvars.d2-config.pad: 80\n\noauth: Authorization Code Flow (PKCE) {\n shape: sequence_diagram\n\n user: User\n browser: Browser\n app: Client App\n auth: Auth Server\n api: Resource API\n\n user -> browser: click \"Sign in\"\n browser -> app: GET /login\n\n authorize: Authorize phase {\n app -> auth: redirect /authorize (code_challenge)\n auth -> browser: consent\n browser -> auth: POST /consent\n auth -> app: redirect + code\n }\n\n token: Token phase {\n app -> auth: POST /token (code + verifier)\n auth -> app: access_token + refresh_token\n }\n\n access: Access phase {\n app -> api: GET /me (Bearer)\n api -> app: 200 { profile }\n app -> browser: render dashboard\n }\n}\n```\n\n---\n\n## 6. ERD — e-commerce core\n\n```d2\nvars.d2-config: {\n theme-id: 0\n pad: 80\n}\n\nclasses: {\n fk: {\n source-arrowhead: {shape: cf-many; label: \"*\"}\n target-arrowhead: {shape: cf-one-required; label: \"1\"}\n }\n}\n\nusers: {\n shape: sql_table\n style.stroke: \"#22c55e\"\n id: uuid {constraint: primary_key}\n email: text {constraint: [unique; not_null]}\n created_at: timestamptz\n}\n\norders: {\n shape: sql_table\n style.stroke: \"#f97316\"\n id: uuid {constraint: primary_key}\n user_id: uuid {constraint: foreign_key}\n total_cents: int\n status: text\n created_at: timestamptz\n}\n\norder_items: {\n shape: sql_table\n style.stroke: \"#f97316\"\n id: uuid {constraint: primary_key}\n order_id: uuid {constraint: foreign_key}\n product_id: uuid {constraint: foreign_key}\n qty: int\n unit_cents: int\n}\n\nproducts: {\n shape: sql_table\n style.stroke: \"#8b5cf6\"\n id: uuid {constraint: primary_key}\n sku: text {constraint: unique}\n name: text\n price_cents: int\n}\n\n(orders.user_id -> users.id)[0].class: fk\n(order_items.order_id -> orders.id)[0].class: fk\n(order_items.product_id -> products.id)[0].class: fk\n```\n\n---\n\n## 7. Kubernetes cluster grid\n\n```d2\nvars.d2-config: {\n theme-id: 0\n pad: 40\n layout-engine: elk\n}\n\nclasses: {\n node: {style: {fill: \"#eff6ff\"; stroke: \"#3b82f6\"}}\n control: {style: {fill: \"#fef2f2\"; stroke: \"#ef4444\"}; shape: hexagon}\n pod: {style: {fill: \"#ecfdf5\"; stroke: \"#10b981\"}; shape: rectangle}\n}\n\ncluster: Production Cluster (us-west-2) {\n\n control_plane: Control Plane {\n apiserver.class: control\n scheduler.class: control\n controller_manager.class: control\n etcd: {shape: cylinder; style: {fill: \"#f5f3ff\"; stroke: \"#8b5cf6\"}}\n apiserver -> etcd\n scheduler -> apiserver\n controller_manager -> apiserver\n }\n\n workers: Worker Nodes {\n grid-rows: 2\n grid-columns: 3\n grid-gap: 16\n\n node_01: {\n class: node\n pod_a.class: pod\n pod_b.class: pod\n }\n node_02: {class: node; pod_c.class: pod; pod_d.class: pod}\n node_03: {class: node; pod_e.class: pod; pod_f.class: pod}\n node_04: {class: node; pod_g.class: pod; pod_h.class: pod}\n node_05: {class: node; pod_i.class: pod}\n node_06: {class: node; pod_j.class: pod}\n }\n}\n```\n\n---\n\n## 8. Decision flowchart (incident triage)\n\n```d2\nvars.d2-config.pad: 60\ndirection: down\n\nclasses: {\n start: {shape: circle; style: {fill: \"#ecfdf5\"; stroke: \"#10b981\"}}\n decision: {shape: diamond; style: {fill: \"#fefce8\"; stroke: \"#eab308\"}}\n action: {shape: rectangle; style: {fill: \"#eff6ff\"; stroke: \"#3b82f6\"}}\n end: {shape: oval; style: {fill: \"#f5f3ff\"; stroke: \"#8b5cf6\"}}\n}\n\nalert.class: start\nalert.label: Alert fires\n\ncustomer_impact: Customer impact? {class: decision}\np1: Declare P1 {class: action}\np2: Track as P2 {class: action}\n\nrollback_possible: Recent deploy? {class: decision}\nrollback: Rollback deploy {class: action}\ninvestigate: Investigate {class: action}\n\nresolved.class: end\n\nalert -> customer_impact\ncustomer_impact -> p1: yes\ncustomer_impact -> p2: no\np1 -> rollback_possible\np2 -> investigate\nrollback_possible -> rollback: yes\nrollback_possible -> investigate: no\nrollback -> resolved\ninvestigate -> resolved\n```\n\n---\n\n## 9. Multi-view composition (layers)\n\n```d2\nvars.d2-config: {\n theme-id: 0\n pad: 80\n}\n\n# Root board\ntitle: System overview {\n shape: text\n near: top-center\n style.font-size: 28\n style.bold: true\n}\n\nuser -> web -> api -> db\n\nlayers: {\n deployment: {\n title: Deployment view {\n shape: text\n near: top-center\n style.bold: true\n }\n cluster: k8s {\n grid-rows: 2\n grid-columns: 2\n node1; node2; node3; node4\n }\n }\n\n sequence: {\n title: Sign-in sequence {\n shape: text\n near: top-center\n style.bold: true\n }\n flow: {\n shape: sequence_diagram\n user; web; api; db\n user -> web -> api -> db\n db -> api -> web: result\n }\n }\n}\n```\n\n---\n\n## 10. Animated request lifecycle (steps)\n\n```d2\nvars.d2-config.pad: 60\ndirection: right\n\nclient.shape: person\napi.shape: rectangle\nauth.shape: rectangle\ndb.shape: cylinder\ncache.shape: stored_data\n\nsteps: {\n s1: Request arrives {\n client -> api\n }\n s2: Cache check {\n api -> cache: GET\n cache -> api: MISS\n }\n s3: Auth verify {\n api -> auth: verify token\n auth -> api: ok\n }\n s4: Read + write cache {\n api -> db: SELECT\n db -> api: rows\n api -> cache: SET\n }\n s5: Respond {\n api -> client: 200 OK\n }\n}\n```\n\nRender:\n```\nd2 --animate-interval=1500 lifecycle.d2 lifecycle.gif\n```\n\n---\n\n## Using templates\n\n1. Copy the template that's ~80% the target diagram.\n2. Rename nodes / containers.\n3. Adjust the `classes:` block to match the actual subsystems.\n4. Apply classes via `.class:` or glob.\n5. Walk edges — is each edge style semantically meaningful?\n6. Run validation (`validation.md`).\n","content_type":"text/markdown; charset=utf-8","language":"markdown","size":10555,"content_sha256":"81efb475ffbc0776a2d57a8991657e81bb7f8ea0185347f4e456e2e50020ebd0"},{"filename":"references/validation.md","content":"# Validation Reference\n\nBefore handing a `.d2` file to the user, run it through this checklist. D2 parses loose input silently (typos become rectangles, bad paths silently fail), so manual validation is worth more than a parse-pass.\n\n---\n\n## Table of Contents\n\n- [Pre-flight checklist](#pre-flight-checklist)\n- [Common D2 mistakes (silent failures)](#common-d2-mistakes-silent-failures)\n- [Parser-level verification (optional but cheap)](#parser-level-verification-optional-but-cheap)\n- [Review-by-reading](#review-by-reading)\n- [Handoff statement (what to tell the user)](#handoff-statement-what-to-tell-the-user)\n\n\n## Pre-flight checklist\n\nWalk every section of the file and verify:\n\n### 1. Header\n\n- [ ] `vars.d2-config` block present with `theme-id`, and `layout-engine` if non-default.\n- [ ] If `layout-engine: tala`, a comment in the file notes it requires a paid install.\n- [ ] `direction:` set explicitly at the root.\n\n### 2. Shapes\n\n- [ ] Every `shape:` value appears in `shapes.md`. No typos (`databse`, `cylindar`).\n- [ ] Shape matches the concept (cylinder for DB, queue for MQ, cloud for external, person for user).\n- [ ] No shapes default to `rectangle` accidentally — rectangles should be a choice, not a leftover.\n- [ ] `shape: sequence_diagram`, `sql_table`, `class` only at container level with correct child syntax.\n\n### 3. Containers\n\n- [ ] Containers are used for **semantic groups** (service, cloud, bounded context), not cosmetic grouping.\n- [ ] No container has exactly one child (flatten it).\n- [ ] Nesting depth ≤ 3 levels, or each level carries distinct meaning.\n- [ ] Cross-container edges use correct dotted paths (verify each path resolves).\n\n### 4. Connections\n\n- [ ] Every edge describes one specific relationship — no composite arrows.\n- [ ] Edge direction (`->` vs `\u003c->` vs `--`) matches reality.\n- [ ] Chains (`a -> b -> c`) only when every segment shares the same semantics / label.\n- [ ] Edge style encodes meaning (solid/dashed/animated/color) — no decoration.\n\n### 5. Classes\n\n- [ ] If a style pattern repeats ≥ 2 times, it's a class.\n- [ ] Class names are semantic (`database`, not `purple`).\n- [ ] No inline styles that duplicate a class (unless intentionally overriding).\n\n### 6. Styling\n\n- [ ] All colors come from `vars.color.*` or the `classes:` block — no scattered hex values.\n- [ ] No gradients on small shapes.\n- [ ] No `animated: true` on every edge.\n- [ ] Hex values quoted (`\"#abc123\"`, not `#abc123` — the latter is a comment).\n\n### 7. Labels\n\n- [ ] Edge labels carry a verb (`reads`, `publishes`, `authenticates`); boring edges left unlabeled.\n- [ ] Markdown labels (`|md ... |`) don't exceed ~10 lines.\n- [ ] No labels contain live identifiers from code (e.g., actual hostnames) if the diagram will be public.\n\n### 8. Composition\n\n- [ ] If using `layers:`, each layer answers a distinct question.\n- [ ] If using `scenarios:`, each scenario only contains *deltas* from the baseline.\n- [ ] If using `steps:`, each step genuinely adds content (no cosmetic-only steps).\n- [ ] Imports (`@file`, `...@file`) use correct relative paths.\n\n### 9. File hygiene\n\n- [ ] 2-space indentation throughout.\n- [ ] No trailing whitespace.\n- [ ] File ends with a newline.\n- [ ] Comments (`#`) explain *why*, not *what*.\n- [ ] UTF-8 encoding (BOM will break the parser).\n\n---\n\n## Common D2 mistakes (silent failures)\n\nD2 errors tend to be silent — it renders *something*, just not what you meant. Watch for these:\n\n### Shape typos fall back to rectangle\n\n```d2\n# BUG\ndb.shape: cylindar # typo → renders as rectangle, no warning\n\n# FIX\ndb.shape: cylinder\n```\n\nPrevention: put shape into a class (`classes.db.shape: cylinder`). Typo there fires once visibly.\n\n### Unquoted hex becomes a comment\n\n```d2\n# BUG\nx.style.fill: #f5f3ff # parsed as comment — no fill applied\n\n# FIX\nx.style.fill: \"#f5f3ff\"\n```\n\n### Edge class applied to node\n\n```d2\n# BUG\nx -> y.class: async # applies to node y, not the edge\n\n# FIX\n(x -> y)[0].class: async\n```\n\n### Dotted path to nonexistent node creates it silently\n\n```d2\n# BUG (if `db` isn't declared in aws)\naws.api -> aws.db # silently creates aws.db as an empty rectangle\n\n# FIX — declare first\naws: {\n api\n db.shape: cylinder\n api -> db\n}\n```\n\n### Container direction under dagre is ignored\n\n```d2\n# BUG (dagre flattens child direction)\ndirection: down\naws: {\n direction: right # ignored by dagre\n api -> db\n}\n\n# FIX — use elk\nvars.d2-config.layout-engine: elk\n```\n\n### Array separator `,` vs `;`\n\n```d2\n# BUG\nx.class: [base, critical] # comma — parse error\n\n# FIX\nx.class: [base; critical]\n```\n\n### Spread import brings only shapes, not vars\n\n```d2\n# BUG — vars in shared.d2 not visible here\[email protected]\nx.style.fill: ${color.primary} # ${color.primary} undefined if shared.d2 has layers/scenarios\n```\n\nPrevention: keep `vars:` / `classes:` in dedicated \"tokens\" files without `layers:` / `scenarios:`.\n\n### Unknown style keys silently ignored\n\n```d2\n# BUG — D2 has no `style.color`; the fill stays default\nx.style.color: \"#ff0000\"\n\n# FIX — use `fill` / `stroke` / `font-color`\nx.style.font-color: \"#ff0000\"\n```\n\nKeep `styles.md` open while reviewing — unknown keys are the single most common silent failure.\n\n### `near:` vs `label.near:` confusion\n\n```d2\n# These do different things\ntitle.near: top-center # places shape on canvas\ntitle.label.near: top-center # places shape's label relative to shape\n```\n\n### `\u003c->` arrow where `->` is correct\n\n```d2\n# BUG — double-arrow implies symmetric relationship\nclient \u003c-> server # if client initiates and server responds, this is wrong\n\n# FIX\nclient -> server # request\nclient \u003c- server: response # or make the response explicit\n```\n\n### `_` parent ref at root is a no-op\n\n```d2\n# BUG — silently does nothing\n_.foo.style.fill: red # at root level, `_` is nothing\n\n# FIX — use direct path\nfoo.style.fill: red\n```\n\n---\n\n## Parser-level verification (optional but cheap)\n\nIf the user has `d2` installed, suggest a parse dry-run:\n\n```sh\nd2 --dry-run input.d2 # exits 0 if parses; non-zero on error\n```\n\nDry-run catches structural errors (unclosed braces, invalid tokens) but **not** semantic ones (wrong shape, bad relationship). The checklist above is still necessary.\n\nFor live iteration:\n\n```sh\nd2 --watch input.d2 # opens browser, live-reloads on save\n```\n\n---\n\n## Review-by-reading\n\nAfter the checklist, read the file top to bottom out loud. If any line requires an explanation that isn't already in the diagram or a comment, the diagram is incomplete.\n\nA second test: open the source in a text editor and ask \"does a reader who doesn't know the system *and* can't run D2 get the shape of the architecture just from the IDs, containers, and edges?\". Indentation alone should convey most of the structure.\n\n---\n\n## Handoff statement (what to tell the user)\n\nWhen returning a finished `.d2` file, tell the user:\n\n1. **Path** — where the file lives (default `docs/architecture/\u003cname>.d2`).\n2. **Render command** — `d2 \u003cname>.d2 \u003cname>.svg` (or PNG / PDF / GIF as appropriate).\n3. **Layout engine** — mention if non-default (`--layout=elk` or `--layout=tala`).\n4. **Theme** — if you set a specific `theme-id`, note which one.\n5. **External assets** — if the diagram references icons by URL, note whether they need mirroring internally (use `d2 --bundle` to inline).\n6. **Follow-up diagrams** — if the concept would benefit from additional views (deployment, sequence, ERD), suggest them.\n\nExample:\n\n> Saved as `docs/architecture/order-service.d2`. Render with\n> `d2 --layout=elk docs/architecture/order-service.d2 docs/architecture/order-service.svg`.\n> Uses theme 0 (neutral) + dark theme 200 for light/dark. Icons pulled from\n> `icons.terrastruct.com`; run with `d2 --bundle` if you need offline rendering.\n> Consider a follow-up sequence diagram for the checkout flow.\n","content_type":"text/markdown; charset=utf-8","language":"markdown","size":7985,"content_sha256":"ff642f03d280b34409e34eb7a12b6b362966a85ce97a89b06d4ef132f393b942"},{"filename":"references/vars.md","content":"# Vars Reference\n\n`vars:` is D2's single source of truth for variables, theme config, and design tokens. One well-organized `vars:` block at the top of the file is the difference between \"palette change = 40 edits\" and \"palette change = 1 line\".\n\n---\n\n## Table of Contents\n\n- [Basic syntax](#basic-syntax)\n- [Nested vars](#nested-vars)\n- [`d2-config` — renderer & theme settings](#d2-config-renderer-theme-settings)\n- [Layout engine config](#layout-engine-config)\n- [Design-token pattern](#design-token-pattern)\n- [Vars in sub-containers](#vars-in-sub-containers)\n- [Vars can't be computed](#vars-cant-be-computed)\n- [Vars with shape / class / style values](#vars-with-shape-class-style-values)\n- [Environment overrides](#environment-overrides)\n- [Anti-patterns](#anti-patterns)\n\n\n## Basic syntax\n\n```d2\nvars: {\n primary-color: \"#3b82f6\"\n danger-color: \"#dc2626\"\n emphasis-border-width: 3\n}\n\napi.style.fill: ${primary-color}\ndb.style.stroke: ${danger-color}\ncritical.style.stroke-width: ${emphasis-border-width}\n```\n\n- Declared under a top-level `vars:` map.\n- Referenced as `${name}`.\n- Values can be strings, numbers, or nested maps.\n- Substitution happens at parse time — no runtime computation.\n\n---\n\n## Nested vars\n\nGroup related variables:\n\n```d2\nvars: {\n palette: {\n frontend: \"#eff6ff\"\n backend: \"#ecfdf5\"\n database: \"#f5f3ff\"\n }\n strokes: {\n frontend: \"#3b82f6\"\n backend: \"#10b981\"\n database: \"#8b5cf6\"\n }\n}\n\nclasses: {\n frontend: {\n style.fill: ${palette.frontend}\n style.stroke: ${strokes.frontend}\n }\n}\n```\n\nDotted access mirrors container dotted paths.\n\n---\n\n## `d2-config` — renderer & theme settings\n\n`vars.d2-config` is a reserved block D2 reads to configure rendering.\n\n```d2\nvars: {\n d2-config: {\n theme-id: 1 # light theme ID\n dark-theme-id: 200 # dark theme ID\n sketch: false # hand-drawn look when true\n pad: 100 # canvas padding, px\n center: true # center diagram in viewport\n layout-engine: elk # dagre | elk | tala\n }\n}\n```\n\n### Theme IDs (light)\n\n| ID | Name |\n|---|---|\n| 0 | Neutral default |\n| 1 | Neutral gray |\n| 3 | Flagship terrastruct |\n| 4 | Cool classics |\n| 5 | Mixed berry blue |\n| 6 | Grape soda |\n| 7 | Aubergine |\n| 8 | Colorblind clear |\n| 100 | Shallow sea |\n| 101 | Dark mauve |\n| 102 | Dark flagship terrastruct |\n\n### Theme IDs (dark)\n\n| ID | Name |\n|---|---|\n| 200 | Dark mauve |\n| 201 | Dark flagship |\n\nPair one of each for light/dark switching: `theme-id: 0` + `dark-theme-id: 200`.\n\n### sketch mode\n\n```d2\nvars.d2-config.sketch: true\n```\n\nHand-drawn look — useful for brainstorming diagrams, whiteboard vibes. Tone-down for production docs.\n\n---\n\n## Layout engine config\n\n```d2\nvars.d2-config.layout-engine: tala\n```\n\n- `dagre` (default, bundled)\n- `elk` (bundled since v0.7)\n- `tala` (paid, must be installed: https://d2lang.com/tour/tala)\n\nSee `layouts.md` for when to pick which.\n\n---\n\n## Design-token pattern\n\nA common layout for production `.d2` files:\n\n```d2\nvars: {\n d2-config: {\n theme-id: 0\n dark-theme-id: 200\n pad: 80\n }\n\n color: {\n frontend: \"#eff6ff\"\n backend: \"#ecfdf5\"\n database: \"#f5f3ff\"\n cache: \"#fef2f2\"\n queue: \"#fefce8\"\n error: \"#dc2626\"\n async: \"#06b6d4\"\n }\n\n stroke: {\n frontend: \"#3b82f6\"\n backend: \"#10b981\"\n database: \"#8b5cf6\"\n cache: \"#ef4444\"\n queue: \"#eab308\"\n }\n\n size: {\n body: 14\n title: 24\n }\n}\n\nclasses: {\n frontend: {\n style: {\n fill: ${color.frontend}\n stroke: ${stroke.frontend}\n font-size: ${size.body}\n }\n }\n backend: {\n style: {\n fill: ${color.backend}\n stroke: ${stroke.backend}\n font-size: ${size.body}\n }\n }\n db: {\n shape: cylinder\n style: {\n fill: ${color.database}\n stroke: ${stroke.database}\n font-size: ${size.body}\n }\n }\n cache: {\n shape: stored_data\n style: {\n fill: ${color.cache}\n stroke: ${stroke.cache}\n }\n }\n async: {\n style: {\n stroke: ${color.async}\n stroke-dash: 4\n animated: true\n }\n }\n error: {\n style: {\n stroke: ${color.error}\n stroke-dash: 2\n }\n }\n}\n\ndirection: right\n\n# …the actual diagram\nuser.shape: person\nfrontend: {\n web.class: frontend\n}\nbackend: {\n api.class: backend\n worker.class: backend\n}\ndata: {\n primary.class: db\n cache.class: cache\n}\n\nuser -> frontend.web -> backend.api\nbackend.api -> data.primary\nbackend.api -> data.cache\n(backend.api -> backend.worker)[0].class: async\n```\n\nChange `${color.frontend}` in one place → every frontend node retints. This is the idiomatic \"theme the diagram once\" pattern.\n\n---\n\n## Vars in sub-containers\n\nVars declared at the root are visible everywhere. Vars declared inside a container are local to that container.\n\n```d2\nvars: {\n global-bg: \"#f9fafb\"\n}\n\naws: {\n vars: {\n aws-color: \"#f97316\"\n }\n style.fill: ${aws-color}\n\n api.style.fill: ${global-bg} # root var visible here\n}\n\n# ${aws-color} is NOT visible at the root level\n```\n\nKeep vars at root unless you genuinely need container-scoped design tokens (rare).\n\n---\n\n## Vars can't be computed\n\nD2's `${...}` is pure text substitution — no arithmetic, no conditionals, no concatenation at reference time.\n\n```d2\nvars.base: 10\n# NOT allowed: x.style.stroke-width: ${base} + 2\n```\n\nIf you need derived values, declare each one explicitly:\n\n```d2\nvars: {\n thin: 1\n medium: 2\n thick: 4\n}\n```\n\n---\n\n## Vars with shape / class / style values\n\nVars can hold more than colors — any value slot works.\n\n```d2\nvars: {\n default-shape: cylinder\n default-stroke-width: 2\n}\n\ndb1.shape: ${default-shape}\ndb2.shape: ${default-shape}\nedge.style.stroke-width: ${default-stroke-width}\n```\n\nFor entire style bundles, prefer `classes:` — vars are for scalars.\n\n---\n\n## Environment overrides\n\nD2 supports environment-driven vars via `--var`:\n\n```sh\nd2 --var=theme-id=5 architecture.d2 architecture.svg\n```\n\nThis overrides `vars.d2-config.theme-id` at render time without editing the file. Useful for CI pipelines that publish light and dark variants from the same source.\n\n---\n\n## Anti-patterns\n\n- **Hardcoded hex scattered through the file** — convert to `vars.color.*` the moment you see two uses.\n- **Vars for one-off values** — if a color appears exactly once, inline it; vars are overhead for singletons.\n- **Deep nesting (`vars.ui.theme.light.palette.primary`)** — flatten to `vars.color.primary`; the grouping isn't worth the typing.\n- **Naming vars after appearance (`vars.blue-1`)** — semantic names (`vars.color.frontend`) survive palette changes.\n- **Using vars for values that should be classes** — if you're repeating `style: {fill: ${x}; stroke: ${y}}` you want a class, not three vars.\n- **Expecting var interpolation in markdown labels** — `${}` only works in attribute values, not inside `|md ... |` blocks.\n","content_type":"text/markdown; charset=utf-8","language":"markdown","size":6899,"content_sha256":"ae665e66e42d3b560df73ca81cba1f2b15a2d15675b80f282f2ade0d72551e4e"}],"content_json":{"type":"doc","content":[{"type":"heading","attrs":{"level":1},"content":[{"text":"D2 Diagram Author","type":"text"}]},{"type":"paragraph","content":[{"text":"Generate idiomatic ","type":"text"},{"text":".d2","type":"text","marks":[{"type":"code_inline"}]},{"text":" source files — diagrams encoded as declarative text, rendered by the official ","type":"text"},{"text":"d2","type":"text","marks":[{"type":"code_inline"}]},{"text":" CLI to SVG / PNG / PDF / GIF. The output is human-readable, diffable, and embeds anywhere D2 embeds (GitHub, Notion, VS Code, docs sites, Oxide's play.d2lang.com).","type":"text"}]},{"type":"paragraph","content":[{"text":"Keep this entry file small. Load only the reference packs that match the current diagram.","type":"text"}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Always-On Rules","type":"text"}]},{"type":"ordered_list","attrs":{"order":1,"listStyle":"number"},"content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Output is ","type":"text","marks":[{"type":"strong"}]},{"text":".d2","type":"text","marks":[{"type":"code_inline"},{"type":"strong"}]},{"text":" text, nothing else.","type":"text","marks":[{"type":"strong"}]},{"text":" The skill never invokes a renderer; the user runs ","type":"text"},{"text":"d2 input.d2 out.svg","type":"text","marks":[{"type":"code_inline"}]},{"text":" or opens play.d2lang.com. Quality must be enforceable by reading the source.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Idiomatic D2 over clever D2.","type":"text","marks":[{"type":"strong"}]},{"text":" Prefer declarative node names (","type":"text"},{"text":"api -> db","type":"text","marks":[{"type":"code_inline"}]},{"text":") over IDs + labels when the name reads naturally. Reach for ","type":"text"},{"text":"label:","type":"text","marks":[{"type":"code_inline"}]},{"text":" only when the natural name would be awkward or duplicated.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Shape type matches concept.","type":"text","marks":[{"type":"strong"}]},{"text":" A database is ","type":"text"},{"text":"shape: cylinder","type":"text","marks":[{"type":"code_inline"}]},{"text":", a queue is ","type":"text"},{"text":"shape: queue","type":"text","marks":[{"type":"code_inline"}]},{"text":", an actor is ","type":"text"},{"text":"shape: person","type":"text","marks":[{"type":"code_inline"}]},{"text":". Never default everything to ","type":"text"},{"text":"rectangle","type":"text","marks":[{"type":"code_inline"}]},{"text":". Full catalog in ","type":"text"},{"text":"references/shapes.md","type":"text","marks":[{"type":"code_inline"}]},{"text":".","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Direction declared up front.","type":"text","marks":[{"type":"strong"}]},{"text":" Set ","type":"text"},{"text":"direction: down | right | up | left","type":"text","marks":[{"type":"code_inline"}]},{"text":" on the root (and per container if needed). Layout direction is a communication choice, not an incidental default.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Semantic edge style, not ornamental.","type":"text","marks":[{"type":"strong"}]},{"text":" ","type":"text"},{"text":"stroke-dash","type":"text","marks":[{"type":"code_inline"}]},{"text":" for async / weak dependency; ","type":"text"},{"text":"animated: true","type":"text","marks":[{"type":"code_inline"}]},{"text":" for streams; ","type":"text"},{"text":"stroke","type":"text","marks":[{"type":"code_inline"}]},{"text":" color to encode criticality / failure paths. Catalog in ","type":"text"},{"text":"references/connections.md","type":"text","marks":[{"type":"code_inline"}]},{"text":".","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Group by container, not by proximity.","type":"text","marks":[{"type":"strong"}]},{"text":" If nodes belong to one domain (a subsystem, a cloud, a bounded context), nest them in a container map. Containers are D2's native grouping — use them instead of manual placement.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"One concept per diagram.","type":"text","marks":[{"type":"strong"}]},{"text":" If a diagram answers two questions, split into two ","type":"text"},{"text":"layers:","type":"text","marks":[{"type":"code_inline"}]},{"text":" or two files. Don't compress.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Classes for repetition.","type":"text","marks":[{"type":"strong"}]},{"text":" ≥ 2 nodes share styling → define a class in ","type":"text"},{"text":"classes: {}","type":"text","marks":[{"type":"code_inline"}]},{"text":" and apply with ","type":"text"},{"text":".class: name","type":"text","marks":[{"type":"code_inline"}]},{"text":" or a glob.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"No invented shapes.","type":"text","marks":[{"type":"strong"}]},{"text":" All ","type":"text"},{"text":"shape:","type":"text","marks":[{"type":"code_inline"}]},{"text":" values must come from ","type":"text"},{"text":"references/shapes.md","type":"text","marks":[{"type":"code_inline"}]},{"text":". D2 silently falls back to rectangle on typos, which is the worst failure mode: it looks right but isn't.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Commit-ready source.","type":"text","marks":[{"type":"strong"}]},{"text":" File ends with a newline, uses 2-space indentation, no trailing whitespace, comments explain intent not mechanics. D2 is git-friendly — keep it that way.","type":"text"}]}]}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Core Workflow","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Step 1: Choose Diagram Family","type":"text"}]},{"type":"table","attrs":{"layout":null},"content":[{"type":"tr","content":[{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Family","type":"text"}]}]},{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"D2 primitive","type":"text"}]}]},{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Use when","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Architecture / flow","type":"text","marks":[{"type":"strong"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"default (nodes + connections)","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Systems, pipelines, dependency graphs","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Sequence","type":"text","marks":[{"type":"strong"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"shape: sequence_diagram","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Protocol exchanges, API call flows, timing","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"ERD / schema","type":"text","marks":[{"type":"strong"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"shape: sql_table","type":"text","marks":[{"type":"code_inline"}]},{"text":" per table","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Data model, foreign keys","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"UML class","type":"text","marks":[{"type":"strong"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"shape: class","type":"text","marks":[{"type":"code_inline"}]},{"text":" per class","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"OO design, type relationships","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Grid / matrix","type":"text","marks":[{"type":"strong"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"grid-rows","type":"text","marks":[{"type":"code_inline"}]},{"text":" / ","type":"text"},{"text":"grid-columns","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Tabular layouts, comparison matrices, k8s cluster views","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Multi-board","type":"text","marks":[{"type":"strong"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"layers:","type":"text","marks":[{"type":"code_inline"}]},{"text":" / ","type":"text"},{"text":"scenarios:","type":"text","marks":[{"type":"code_inline"}]},{"text":" / ","type":"text"},{"text":"steps:","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Before/after, drill-down, animation","type":"text"}]}]}]}]},{"type":"paragraph","content":[{"text":"Details per family in ","type":"text"},{"text":"references/special-diagrams.md","type":"text","marks":[{"type":"code_inline"}]},{"text":" and ","type":"text"},{"text":"references/composition.md","type":"text","marks":[{"type":"code_inline"}]},{"text":".","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Step 2: Pick Layout Engine","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"dagre","type":"text","marks":[{"type":"strong"}]},{"text":" (default, free, bundled): DAG-oriented, good for most architecture diagrams.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"elk","type":"text","marks":[{"type":"strong"}]},{"text":" (free, bundled since v0.7): cleaner for nested hierarchies and orthogonal routing. Worth trying for multi-container diagrams.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"tala","type":"text","marks":[{"type":"strong"}]},{"text":" (paid, separate binary): best-in-class for complex, dense diagrams. Note in the file header if required so the user knows what to install.","type":"text"}]}]}]},{"type":"paragraph","content":[{"text":"Pick once per file via ","type":"text"},{"text":"vars.d2-config.layout-engine","type":"text","marks":[{"type":"code_inline"}]},{"text":". Don't fight the engine with manual positions — D2 has no ","type":"text"},{"text":"x,y","type":"text","marks":[{"type":"code_inline"}]},{"text":".","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Step 3: Draft Structure","type":"text"}]},{"type":"ordered_list","attrs":{"order":1,"listStyle":"number"},"content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Sketch the top-level containers first: which systems / domains exist.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Add nodes inside each container with the correct ","type":"text"},{"text":"shape:","type":"text","marks":[{"type":"code_inline"}]},{"text":".","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Add connections — direction first, labels after topology is right.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Apply ","type":"text"},{"text":"classes:","type":"text","marks":[{"type":"code_inline"}]},{"text":" to collapse repetition.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Add ","type":"text"},{"text":"vars.d2-config","type":"text","marks":[{"type":"code_inline"}]},{"text":" block (theme, layout engine) at the top.","type":"text"}]}]}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Step 4: Style Pass","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Walk every connection — is its style (solid / dashed / animated / color) semantically distinct from its neighbors'?","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Walk every node — does its ","type":"text"},{"text":"shape:","type":"text","marks":[{"type":"code_inline"}]},{"text":" and class carry information?","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Remove ornamentation that doesn't encode meaning (decorative colors, gratuitous shadows).","type":"text"}]}]}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Step 5: Validate & Hand Off","type":"text"}]},{"type":"paragraph","content":[{"text":"Run the pre-flight in ","type":"text"},{"text":"references/validation.md","type":"text","marks":[{"type":"code_inline"}]},{"text":". Tell the user:","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"The output path (default ","type":"text"},{"text":"docs/architecture/\u003cname>.d2","type":"text","marks":[{"type":"code_inline"}]},{"text":").","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"The render command: ","type":"text"},{"text":"d2 \u003cname>.d2 \u003cname>.svg","type":"text","marks":[{"type":"code_inline"}]},{"text":" (or whatever target they need).","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"The chosen layout engine and whether it requires a separate install.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Any icons referenced by URL (so they can mirror internally if needed).","type":"text"}]}]}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Section-by-Section for Large Diagrams","type":"text"}]},{"type":"ordered_list","attrs":{"order":1,"listStyle":"number"},"content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Write the ","type":"text"},{"text":"vars","type":"text","marks":[{"type":"code_inline"}]},{"text":" + ","type":"text"},{"text":"direction","type":"text","marks":[{"type":"code_inline"}]},{"text":" header and one container.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Append one container per edit; prefix child IDs by container if they'd otherwise collide.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Cross-container edges use dotted paths (","type":"text"},{"text":"aws.api -> gcp.auth","type":"text","marks":[{"type":"code_inline"}]},{"text":"); verify the path exists before emitting the edge.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"When a diagram grows past ~60 nodes, split into ","type":"text"},{"text":"layers:","type":"text","marks":[{"type":"code_inline"}]},{"text":" or separate ","type":"text"},{"text":".d2","type":"text","marks":[{"type":"code_inline"}]},{"text":" files composed via ","type":"text"},{"text":"@file","type":"text","marks":[{"type":"code_inline"}]},{"text":" imports (","type":"text"},{"text":"references/composition.md","type":"text","marks":[{"type":"code_inline"}]},{"text":").","type":"text"}]}]}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Output","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"File","type":"text","marks":[{"type":"strong"}]},{"text":": ","type":"text"},{"text":"docs/architecture/\u003cname>.d2","type":"text","marks":[{"type":"code_inline"}]},{"text":" by default, or a path the user specifies.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Render (user runs)","type":"text","marks":[{"type":"strong"}]},{"text":":","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"d2 name.d2","type":"text","marks":[{"type":"code_inline"}]},{"text":" → ","type":"text"},{"text":"name.svg","type":"text","marks":[{"type":"code_inline"}]},{"text":" (default)","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"d2 name.d2 name.png","type":"text","marks":[{"type":"code_inline"}]},{"text":" / ","type":"text"},{"text":"name.pdf","type":"text","marks":[{"type":"code_inline"}]},{"text":" / ","type":"text"},{"text":"name.gif","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"d2 --watch name.d2","type":"text","marks":[{"type":"code_inline"}]},{"text":" → live preview on localhost","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"d2 --theme \u003cid> --dark-theme \u003cid> name.d2","type":"text","marks":[{"type":"code_inline"}]},{"text":" → theme pairing","type":"text"}]}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Web preview","type":"text","marks":[{"type":"strong"}]},{"text":": paste into ","type":"text"},{"text":"https://play.d2lang.com/","type":"text","marks":[{"type":"link","attrs":{"href":"https://play.d2lang.com/","title":null}}]},{"text":" for instant rendering without installing D2.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Embedding","type":"text","marks":[{"type":"strong"}]},{"text":": see ","type":"text"},{"text":"references/integration.md","type":"text","marks":[{"type":"code_inline"}]},{"text":" for MDX, GitHub READMEs (checked-in SVG), docs sites (live render at build time), and CI pipelines.","type":"text"}]}]}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Reference Packs","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"references/syntax.md","type":"text","marks":[{"type":"code_inline"}]},{"text":" Core grammar: identifiers, labels, maps, comments, escaping, the shorthand rules D2 is full of.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"references/shapes.md","type":"text","marks":[{"type":"code_inline"}]},{"text":" Every built-in shape with picture description, data semantics, and \"use when\" guidance.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"references/connections.md","type":"text","marks":[{"type":"code_inline"}]},{"text":" Edge operators (","type":"text"},{"text":"->","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"\u003c-","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"\u003c->","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"--","type":"text","marks":[{"type":"code_inline"}]},{"text":"), chaining, arrowheads, labels, semantic styling patterns.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"references/containers.md","type":"text","marks":[{"type":"code_inline"}]},{"text":" Nesting, ","type":"text"},{"text":"_","type":"text","marks":[{"type":"code_inline"}]},{"text":" parent refs, dotted paths, the difference between a container and a shape, when to flatten vs nest.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"references/classes.md","type":"text","marks":[{"type":"code_inline"}]},{"text":" ","type":"text"},{"text":"classes:","type":"text","marks":[{"type":"code_inline"}]},{"text":" block, applying via ","type":"text"},{"text":".class:","type":"text","marks":[{"type":"code_inline"}]},{"text":", multi-class arrays, glob-based mass application.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"references/styles.md","type":"text","marks":[{"type":"code_inline"}]},{"text":" Complete style key catalog (fill, stroke, stroke-dash, shadow, 3d, multiple, animated, fill-pattern, border-radius, font-size, bold/italic/underline, opacity).","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"references/layouts.md","type":"text","marks":[{"type":"code_inline"}]},{"text":" Direction, grid layouts, dagre vs elk vs tala tradeoffs, ","type":"text"},{"text":"near:","type":"text","marks":[{"type":"code_inline"}]},{"text":" positioning for titles and legends.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"references/special-diagrams.md","type":"text","marks":[{"type":"code_inline"}]},{"text":" Sequence diagrams (actors, spans, notes, groups), ","type":"text"},{"text":"sql_table","type":"text","marks":[{"type":"code_inline"}]},{"text":" ERDs, ","type":"text"},{"text":"class","type":"text","marks":[{"type":"code_inline"}]},{"text":" UML, markdown / code / LaTeX blocks.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"references/composition.md","type":"text","marks":[{"type":"code_inline"}]},{"text":" ","type":"text"},{"text":"layers:","type":"text","marks":[{"type":"code_inline"}]},{"text":" (isolated boards), ","type":"text"},{"text":"scenarios:","type":"text","marks":[{"type":"code_inline"}]},{"text":" (inherit + override), ","type":"text"},{"text":"steps:","type":"text","marks":[{"type":"code_inline"}]},{"text":" (animation), ","type":"text"},{"text":"@file","type":"text","marks":[{"type":"code_inline"}]},{"text":" imports, ","type":"text"},{"text":"...@file","type":"text","marks":[{"type":"code_inline"}]},{"text":" spread.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"references/vars.md","type":"text","marks":[{"type":"code_inline"}]},{"text":" ","type":"text"},{"text":"vars:","type":"text","marks":[{"type":"code_inline"}]},{"text":" block, ","type":"text"},{"text":"${var}","type":"text","marks":[{"type":"code_inline"}]},{"text":" substitution, ","type":"text"},{"text":"d2-config","type":"text","marks":[{"type":"code_inline"}]},{"text":" (theme-id, dark-theme-id, sketch, pad, center, layout-engine), nested vars for design tokens.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"references/templates.md","type":"text","marks":[{"type":"code_inline"}]},{"text":" Copy-paste starter files: 3-tier web, microservices, data pipeline, CI/CD, sequence (OAuth), ERD, k8s cluster (grid).","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"references/design.md","type":"text","marks":[{"type":"code_inline"}]},{"text":" Concept→pattern mapping, isomorphism test, when D2 beats ReactFlow / Mermaid / Excalidraw, when it loses.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"references/validation.md","type":"text","marks":[{"type":"code_inline"}]},{"text":" Pre-flight algorithm and checklists; common D2 mistakes (silent shape fallback, missing container path, label vs comment collisions).","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"references/render.md","type":"text","marks":[{"type":"code_inline"}]},{"text":" CLI flags, watch mode, playground, icon catalogs (terrastruct icons, svgrepo), theme gallery.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"references/integration.md","type":"text","marks":[{"type":"code_inline"}]},{"text":" MDX / Docusaurus / Nextra embedding, GitHub README via checked-in SVG, docs-site build pipelines, pre-commit render.","type":"text"}]}]}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Quick Routing","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"New to D2 syntax: load ","type":"text"},{"text":"syntax.md","type":"text","marks":[{"type":"code_inline"}]},{"text":" + ","type":"text"},{"text":"shapes.md","type":"text","marks":[{"type":"code_inline"}]},{"text":" + ","type":"text"},{"text":"connections.md","type":"text","marks":[{"type":"code_inline"}]},{"text":".","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Architecture / system diagram: ","type":"text"},{"text":"shapes.md","type":"text","marks":[{"type":"code_inline"}]},{"text":" + ","type":"text"},{"text":"containers.md","type":"text","marks":[{"type":"code_inline"}]},{"text":" + ","type":"text"},{"text":"layouts.md","type":"text","marks":[{"type":"code_inline"}]},{"text":" + ","type":"text"},{"text":"templates.md","type":"text","marks":[{"type":"code_inline"}]},{"text":".","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Sequence / SQL / UML: ","type":"text"},{"text":"special-diagrams.md","type":"text","marks":[{"type":"code_inline"}]},{"text":" + ","type":"text"},{"text":"templates.md","type":"text","marks":[{"type":"code_inline"}]},{"text":".","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Styling pass: ","type":"text"},{"text":"classes.md","type":"text","marks":[{"type":"code_inline"}]},{"text":" + ","type":"text"},{"text":"styles.md","type":"text","marks":[{"type":"code_inline"}]},{"text":".","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Multi-board story (before/after, drill-down, animation): ","type":"text"},{"text":"composition.md","type":"text","marks":[{"type":"code_inline"}]},{"text":".","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Theming / config: ","type":"text"},{"text":"vars.md","type":"text","marks":[{"type":"code_inline"}]},{"text":" + ","type":"text"},{"text":"render.md","type":"text","marks":[{"type":"code_inline"}]},{"text":".","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Before delivering: ","type":"text"},{"text":"validation.md","type":"text","marks":[{"type":"code_inline"}]},{"text":".","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Telling the user how to render or embed: ","type":"text"},{"text":"render.md","type":"text","marks":[{"type":"code_inline"}]},{"text":" + ","type":"text"},{"text":"integration.md","type":"text","marks":[{"type":"code_inline"}]},{"text":".","type":"text"}]}]}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"If the project also uses ","type":"text"},{"text":"/pma","type":"text","marks":[{"type":"code_inline"}]},{"text":" for workflow control, load ","type":"text"},{"text":"/pma","type":"text","marks":[{"type":"code_inline"}]},{"text":" first, then ","type":"text"},{"text":"/pma-d2","type":"text","marks":[{"type":"code_inline"}]},{"text":" only when a diagram is required. If the project needs interactive, in-browser diagrams with custom React node components, use ","type":"text"},{"text":"/pma-draw","type":"text","marks":[{"type":"code_inline"}]},{"text":" instead — D2 renders to static images.","type":"text"}]}]},"metadata":{"date":"2026-06-05","name":"pma-d2","author":"@skillopedia","source":{"stars":2,"repo_name":"skills","origin_url":"https://github.com/zzci/skills/blob/HEAD/backup/pma-d2/SKILL.md","repo_owner":"zzci","body_sha256":"495905eba58aced7cdb5963dd50a0a34b1935b79527c96115eecbd3cb8a338fd","cluster_key":"bf66fa1960792c449a4df8019b7ce64d3d64adf16313f66d16f89ce07d542e84","clean_bundle":{"format":"clean-skill-bundle-v1","source":"zzci/skills/backup/pma-d2/SKILL.md","attachments":[{"id":"57a81e14-bcfb-5ac7-a975-65adfed42f26","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/57a81e14-bcfb-5ac7-a975-65adfed42f26/attachment.yaml","path":"agents/openai.yaml","size":186,"sha256":"7cd10068960fc2035bf2f46cd01c2a9c50504477296ee6ed96dc6a78e5512157","contentType":"application/yaml; charset=utf-8"},{"id":"b4e5bb89-e0b0-5378-8f22-145e1964e8b0","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/b4e5bb89-e0b0-5378-8f22-145e1964e8b0/attachment.md","path":"references/classes.md","size":4794,"sha256":"71101881c4c855b6d6e72f854c6a26b4332cac6b3dad42e3fad2f04cdacf38a7","contentType":"text/markdown; charset=utf-8"},{"id":"ff113415-11eb-54dc-ae5e-c7a04362ebce","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/ff113415-11eb-54dc-ae5e-c7a04362ebce/attachment.md","path":"references/composition.md","size":8458,"sha256":"282daf85ca97d43ca93fecc7ff180b06b664c9fbbb2f2bc5e45e980b05ffd486","contentType":"text/markdown; charset=utf-8"},{"id":"f2df2195-4168-585b-b060-d9c72a17a832","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/f2df2195-4168-585b-b060-d9c72a17a832/attachment.md","path":"references/connections.md","size":6284,"sha256":"6aa964d97b021cea93e1965d5799b7e6a8ca002d35cc572f8bc83ec99bba1437","contentType":"text/markdown; charset=utf-8"},{"id":"485bae19-c2d3-5603-bb11-d21497fcdfd7","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/485bae19-c2d3-5603-bb11-d21497fcdfd7/attachment.md","path":"references/containers.md","size":6327,"sha256":"804124cd732f59883210010f246df0edb794f936c830e34d5d66341e812295d5","contentType":"text/markdown; charset=utf-8"},{"id":"af32278f-033e-5a74-a65d-eecbca8c7770","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/af32278f-033e-5a74-a65d-eecbca8c7770/attachment.md","path":"references/design.md","size":8271,"sha256":"561001b4fb40cbc17e9c3af42a84b9f77135216a5a5ef27a1b3fb18ffbd98bde","contentType":"text/markdown; charset=utf-8"},{"id":"bbf62ebc-964a-5e30-860c-0488fbbccdc9","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/bbf62ebc-964a-5e30-860c-0488fbbccdc9/attachment.md","path":"references/integration.md","size":7846,"sha256":"84555e57b1efa5db8c8881da8dfa3b7c3aeedc3d2bce621b5c5ae4a06994a2fd","contentType":"text/markdown; charset=utf-8"},{"id":"32417c45-cec5-5ba9-aa16-81b34ae9b27b","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/32417c45-cec5-5ba9-aa16-81b34ae9b27b/attachment.md","path":"references/layouts.md","size":8141,"sha256":"f012585a74e8996a046ad35aae3c50b1be86b385e666a64580a84e2cdfa316b3","contentType":"text/markdown; charset=utf-8"},{"id":"3b70980f-793d-534a-a423-8cbb8015956d","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/3b70980f-793d-534a-a423-8cbb8015956d/attachment.md","path":"references/render.md","size":6800,"sha256":"c7d7e2d3a994fef11fdaf9101b953fad1439797a3341c01f54c62cafdbf5b4f3","contentType":"text/markdown; charset=utf-8"},{"id":"2004405c-8bc3-56d9-bab3-a9f19d7faa53","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/2004405c-8bc3-56d9-bab3-a9f19d7faa53/attachment.md","path":"references/shapes.md","size":6140,"sha256":"b60693e2361c830458f8858db0aeb7a727aaf520f2d963cdc58f299b1fdd63b2","contentType":"text/markdown; charset=utf-8"},{"id":"6bd17484-b3e8-5f3c-bbb6-a82e607248a7","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/6bd17484-b3e8-5f3c-bbb6-a82e607248a7/attachment.md","path":"references/special-diagrams.md","size":9014,"sha256":"b7bb236c0124a96ea90754039da8d49d69f6f80e7ca1ae8b12fffe96556a011d","contentType":"text/markdown; charset=utf-8"},{"id":"9dca7f9b-fa99-5f96-8566-a3e12eebf7ce","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/9dca7f9b-fa99-5f96-8566-a3e12eebf7ce/attachment.md","path":"references/styles.md","size":6551,"sha256":"fdda2e350a3e4c2ca3baa29c364d5bbf2204c780309b100560cac4dfad4f696f","contentType":"text/markdown; charset=utf-8"},{"id":"10ed1a1e-5b62-5db2-8db3-67950f259cf9","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/10ed1a1e-5b62-5db2-8db3-67950f259cf9/attachment.md","path":"references/syntax.md","size":5193,"sha256":"e59baa047b9a44387a679abcd34f9f394b71665335e8d1982ebe885dc285baee","contentType":"text/markdown; charset=utf-8"},{"id":"46b7fbc1-2402-51be-8858-c3981d0af008","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/46b7fbc1-2402-51be-8858-c3981d0af008/attachment.md","path":"references/templates.md","size":10555,"sha256":"81efb475ffbc0776a2d57a8991657e81bb7f8ea0185347f4e456e2e50020ebd0","contentType":"text/markdown; charset=utf-8"},{"id":"a4dd2847-edaa-5386-9632-1c745808d3ba","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/a4dd2847-edaa-5386-9632-1c745808d3ba/attachment.md","path":"references/validation.md","size":7985,"sha256":"ff642f03d280b34409e34eb7a12b6b362966a85ce97a89b06d4ef132f393b942","contentType":"text/markdown; charset=utf-8"},{"id":"806bb6b4-787a-5d1b-ac6c-dcd8e5318650","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/806bb6b4-787a-5d1b-ac6c-dcd8e5318650/attachment.md","path":"references/vars.md","size":6899,"sha256":"ae665e66e42d3b560df73ca81cba1f2b15a2d15675b80f282f2ade0d72551e4e","contentType":"text/markdown; charset=utf-8"}],"bundle_sha256":"e795aaa52c622c62f149e1e92ca688833567b76a6ceebdd7d58549aac017dad4","attachment_count":16,"text_attachments":16,"attachment_storage":"skillopedia-attachments-v1","binary_attachments":0,"excluded_attachments":[]},"cluster_size":1,"skill_md_path":"backup/pma-d2/SKILL.md","import_metadata":{"date":"2026-06-05","author":"@skillopedia","version":"v1","category":"productivity-workflow","category_label":"Productivity"},"exact_dupes_collapsed_into_this":0},"version":"v1","category":"productivity-workflow","import_tag":"clean-skills-v1","description":"Generate D2 (d2lang.com) `.d2` diagrams from concepts or code. Use when the user wants architecture, workflow, sequence, or ERD diagrams rendered by the official D2 CLI / playground, or wants source-controlled text-based diagrams that diff cleanly in git."}},"renderedAt":1782980591479}

D2 Diagram Author Generate idiomatic source files — diagrams encoded as declarative text, rendered by the official CLI to SVG / PNG / PDF / GIF. The output is human-readable, diffable, and embeds anywhere D2 embeds (GitHub, Notion, VS Code, docs sites, Oxide's play.d2lang.com). Keep this entry file small. Load only the reference packs that match the current diagram. Always-On Rules 1. Output is text, nothing else. The skill never invokes a renderer; the user runs or opens play.d2lang.com. Quality must be enforceable by reading the source. 2. Idiomatic D2 over clever D2. Prefer declarative nod…