DocC Diagram Pipeline
How the architecture and lifecycle diagrams in TUIkit's DocC documentation are created, rendered, and integrated.
Contents
Overview
All diagrams in the DocC documentation catalog are authored in Typst using the fletcher package for node-edge diagrams. The source files are compiled to high-resolution PNGs with transparent backgrounds, then placed in the DocC Resources/ directory with light and dark mode variants.
Previous approaches (Mermaid, D2) were abandoned due to poor rendering quality and limited styling control.
Prerequisites
Typst
Install via Homebrew:
brew install typst
Version used: Typst 0.14.x or later.
Fletcher Package
Fletcher is fetched automatically on first compile via Typst's package manager. No manual installation required. The import line in each .typ file handles this:
#import "@preview/fletcher:0.5.8" as fletcher: diagram, node, edge
Font
All diagrams use SF Pro Text (9-10pt). This font ships with macOS. On Linux CI, the diagrams are pre-compiled, so the font is not required there.
Source Files
Typst source files live in /tmp/ during authoring (not committed). The 10 current diagrams:
| File | Article | Description |
|---|---|---|
render-cycle-pipeline.typ | RenderCycle.md | 12-step render pipeline (zigzag layout) |
render-cycle-dispatch.typ | RenderCycle.md | Renderable vs body dispatch decision tree |
architecture-event-loop.typ | Architecture.md | Full event loop (init, setup, timers, main loop) |
architecture-input-dispatch.typ | Architecture.md | 5-layer input dispatch with focus checks |
keyboard-event-dispatch.typ | KeyboardShortcuts.md | 3-layer keyboard dispatch |
lifecycle-subsystem-init.typ | AppLifecycle.md | @main -> AppRunner -> subsystem init (top-down) |
lifecycle-run-creates.typ | AppLifecycle.md | run() -> runtime components |
lifecycle-main-loop.typ | AppLifecycle.md | Main loop flowchart with shutdown path |
dep-graph-ownership.typ | AppLifecycle.md | AppRunner ownership tree |
dep-graph-references.typ | AppLifecycle.md | RenderLoop/InputHandler runtime references |
Visual Style
All diagrams follow a consistent style:
Colors
// Node fills (green palette, dark-on-transparent)
#let primary-box = (fill: rgb("#1a9e76"), stroke: none, corner-radius: 6pt, inset: 10pt)
#let secondary-box = (fill: rgb("#2e8b7a"), stroke: none, corner-radius: 6pt, inset: 8pt)
#let tertiary-box = (fill: rgb("#287a6b"), stroke: none, corner-radius: 6pt, inset: 8pt)
#let muted-box = (fill: rgb("#5a6b7a"), stroke: none, corner-radius: 6pt, inset: 8pt)
All text inside nodes is white. Annotation text uses light gray rgb("#94a3b8").
Edges (Style D)
#diagram(
edge-stroke: 0.7pt + rgb("#64748b"), // thin, muted gray
mark-scale: 50%, // small filled triangle arrowheads
// ...
)
Key properties:
- Line weight: 0.7pt (thin, unobtrusive)
- Arrow marks: 50% scale, filled triangles
- Color:
#64748b(slate gray, works on both light and dark backgrounds) - Routing: Corner routing where needed to avoid crossing nodes
Page Setup
#set page(width: auto, height: auto, margin: 1.2em, fill: none)
#set text(font: "SF Pro Text", size: 9.5pt)
width: auto, height: autosizes the page to contentfill: noneproduces a transparent background (essential for DocC light/dark mode)- Font size between 9pt and 9.5pt depending on diagram density
Decision Nodes
For flowcharts with yes/no branches, use diamond shapes:
#let check-box = (fill: rgb("#1a9e76"), stroke: none, shape: fletcher.shapes.diamond, inset: 6pt)
Dashed Lines
For containment or grouping relationships (e.g. TUIContext contains sub-components):
edge((1, 3), (1, 5), "--", stroke: 0.7pt + rgb("#8a9ab0"))
Compilation
Single Diagram
cd /tmp
typst compile render-cycle-pipeline.typ render-cycle-pipeline.png --ppi 288
--ppi 288produces 2x Retina resolution (288 / 144 DPI = 2x)- Output is a PNG with transparent background
All Diagrams (Batch)
cd /tmp
for f in render-cycle-pipeline render-cycle-dispatch architecture-event-loop \
architecture-input-dispatch keyboard-event-dispatch lifecycle-subsystem-init \
lifecycle-run-creates lifecycle-main-loop dep-graph-ownership dep-graph-references; do
typst compile "${f}.typ" "${f}.png" --ppi 288
echo "Compiled: ${f}.png"
done
Deployment to DocC
Copy to Resources
DocC expects images in the documentation catalog's Resources/ directory. For light/dark mode support, two files are needed per diagram:
RESOURCES="Sources/TUIkit/TUIkit.docc/Resources"
for f in render-cycle-pipeline render-cycle-dispatch architecture-event-loop \
architecture-input-dispatch keyboard-event-dispatch lifecycle-subsystem-init \
lifecycle-run-creates lifecycle-main-loop dep-graph-ownership dep-graph-references; do
cp "/tmp/${f}.png" "${RESOURCES}/${f}.png"
cp "/tmp/${f}.png" "${RESOURCES}/${f}~dark.png"
done
Since the diagrams use transparent backgrounds with colored nodes and white text, the same PNG works for both light and dark mode. The ~dark suffix is DocC's naming convention for dark mode image variants.
Reference in Articles
In DocC Markdown articles, reference the images with the @Image directive:
@Image(source: "render-cycle-pipeline", alt: "12-step render pipeline")
DocC automatically selects render-cycle-pipeline.png or render-cycle-pipeline~dark.png based on the user's appearance setting.
Layout Patterns
Zigzag (Pipeline)
For long sequential pipelines, avoid a single tall column. Instead, use a zigzag: left column flows top-to-bottom, horizontal bridge at the bottom, right column flows bottom-to-top.
Step 1 Step 12
| ^
Step 2 Step 11
| ^
... ...
| ^
Step 6 -> Step 7
This keeps the diagram compact and avoids arrows crossing through nodes.
Top-Down Tree (Init/Ownership)
For hierarchical relationships, place the root at the top and fan out below:
@main
|
App.main()
|
Self()
|
AppRunner(app:)
/ | | | \
S1 S2 S3 S4 ...
Annotation Columns (Dense Graphs)
When a diagram has many edges that would overlap with edge labels, use separate annotation nodes instead:
RenderLoop [annotation] Subsystem [annotation] InputHandler
(0,4) (1,*) (2,*) (3,*) (4,4)
Annotations are placed in dedicated columns with small italic text in light gray, avoiding label overlap with edges.
Troubleshooting
Arrows Crossing Through Nodes
If arrows from a source to multiple targets cross through intermediate nodes, restructure the layout. Options:
- Reorder nodes so the source is adjacent to its targets
- Use the zigzag pattern
- Place the source in a column/row where direct paths to all targets are clear
Text Too Small After Compilation
Increase --ppi (e.g. 384 for 2.67x). Alternatively, increase the font size in the .typ file. The current 288 PPI at 9.5pt produces good results for DocC web rendering.
Labels Not Visible in Dark Mode
Never use black or dark-colored text for labels or annotations. Use:
- White (
#ffffff) for text inside colored nodes - Light gray (
#94a3b8) for standalone annotations - Avoid edge labels when possible; use annotation columns instead
Fletcher Package Not Found
On first compile, Typst downloads the fletcher package automatically. If it fails:
typst compile --help # verify typst is installed
typst compile test.typ test.png # triggers package download
Ensure network access is available for the initial package fetch.
Adding a New Diagram
- Create
/tmp/new-diagram.typfollowing the style guide above - Compile:
typst compile new-diagram.typ new-diagram.png --ppi 288 - Review the PNG (check readability, arrow routing, transparent background)
- Copy to
Sources/TUIkit/TUIkit.docc/Resources/new-diagram.png - Copy to
Sources/TUIkit/TUIkit.docc/Resources/new-diagram~dark.png - Add
@Image(source: "new-diagram", alt: "Description")in the DocC article - Preview with
swift package --disable-sandbox preview-documentation --target TUIkit