DocC Diagram Pipeline

TUIkit
15 February 2026
PAP-003

How the architecture and lifecycle diagrams in TUIkit's DocC documentation are created, rendered, and integrated.

Contents

  1. Overview
  2. Prerequisites
  3. Source Files
  4. Visual Style
  5. Compilation
  6. Deployment to DocC
  7. Layout Patterns
  8. Troubleshooting
  9. Adding a New Diagram

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:

FileArticleDescription
render-cycle-pipeline.typRenderCycle.md12-step render pipeline (zigzag layout)
render-cycle-dispatch.typRenderCycle.mdRenderable vs body dispatch decision tree
architecture-event-loop.typArchitecture.mdFull event loop (init, setup, timers, main loop)
architecture-input-dispatch.typArchitecture.md5-layer input dispatch with focus checks
keyboard-event-dispatch.typKeyboardShortcuts.md3-layer keyboard dispatch
lifecycle-subsystem-init.typAppLifecycle.md@main -> AppRunner -> subsystem init (top-down)
lifecycle-run-creates.typAppLifecycle.mdrun() -> runtime components
lifecycle-main-loop.typAppLifecycle.mdMain loop flowchart with shutdown path
dep-graph-ownership.typAppLifecycle.mdAppRunner ownership tree
dep-graph-references.typAppLifecycle.mdRenderLoop/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:

Page Setup

#set page(width: auto, height: auto, margin: 1.2em, fill: none)
#set text(font: "SF Pro Text", size: 9.5pt)

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

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:

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:

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

  1. Create /tmp/new-diagram.typ following the style guide above
  2. Compile: typst compile new-diagram.typ new-diagram.png --ppi 288
  3. Review the PNG (check readability, arrow routing, transparent background)
  4. Copy to Sources/TUIkit/TUIkit.docc/Resources/new-diagram.png
  5. Copy to Sources/TUIkit/TUIkit.docc/Resources/new-diagram~dark.png
  6. Add @Image(source: "new-diagram", alt: "Description") in the DocC article
  7. Preview with swift package --disable-sandbox preview-documentation --target TUIkit