Skip to content

ADR-0001 — C4 model rendered as hand-authored D2

  • Status: Accepted
  • Date: 2026-04-28

Context

Architecture diagrams in Markdown drift quickly. We want a stable visual language for the platform's structure (systems, containers, components, relationships) that survives team turnover and renders cleanly in MkDocs Material.

Three options were considered:

  1. Hand-authored Mermaid in Markdown. Easy to write, zero tooling, but expressive ceiling is low — large container diagrams become unreadable, and styling is mostly out of our hands.
  2. Structurizr DSL → static export. Single model, automated rendering. Tried it; the default PlantUML output looked dated, and the DSL → D2 converter we prototyped was a maintenance burden of its own — every new view shape needed converter work before it could be drawn.
  3. Hand-authored D2 per diagram. Each C4 view is its own .d2 file, sharing styles via a single _styles.d2 partial. No model layer, no converter; the diagrams are the source of truth.

Decision

Adopt the C4 model as a documentation framing (Context / Container / Component levels), with each diagram hand-authored as D2 under d2/*.d2 and rendered to SVG by tools/render-diagrams.sh (D2 in Docker, ELK layout, theme 200).

A shared d2/_styles.d2 partial holds the visual vocabulary — palette, classes, edge categories — so every diagram looks like part of the same set without a model layer above. Authoring rules live in d2/STYLE.md.

Sequence diagrams and ER diagrams stay as inline Mermaid in Markdown — D2 is wrong for those shapes.

Consequences

  • Positive: One file per diagram, readable in isolation. No build step beyond d2 itself.
  • Positive: D2 SVGs render cleanly in MkDocs Material; the visual style is refreshable by editing _styles.d2. svg-pan-zoom makes density acceptable in the browser.
  • Positive: No converter to maintain. Every D2 feature is available immediately; layout tuning is local to the diagram.
  • Negative: Diagrams can drift relative to each other if a relationship changes — e.g. a new edge added in containers.d2 must be propagated by hand into the relevant L3 component diagram. Mitigated by review checklist and STYLE.md.
  • Negative: No machine-readable model; we can't query "every place X talks to Y" the way a Structurizr workspace would let us.
  • Negative: D2's auto-layout sometimes needs manual direction: / grid hints; this is expected and called out in STYLE.md.

Implementation

  • D2 sources: d2/*.d2 (one file per C4 view; d2/_styles.d2 is the shared partial).
  • Render script: tools/render-diagrams.sh (and .ps1 for Windows). Runs terrastruct/d2 in Docker, ELK layout, theme 200, then injects explicit width/height so the browser renders at native size for pan/zoom.
  • Output: docs/assets/diagrams/*.svg (gitignored — built locally and in CI).
  • Style and authoring rules: d2/STYLE.md.