Skip to content

diagrams

Ary Rabelo edited this page Jul 22, 2026 · 1 revision

Diagram Rendering

Relevant source files

  • src/repodocs/diagrams.py
  • tests/test_fix_diagrams.py

Overview

repodocs.diagrams is an optional, clone-time post-processing step that finds every ```mermaid fenced block in the generated wiki pages, renders it to a pastel PNG using an external Bun + Playwright tool (tools/diagram_poster.ts), and rewrites the page to embed the image instead of the raw fence. GitHub's built-in wiki mermaid renderer is described as intermittently failing to load, so a committed PNG is used as a more reliable fallback. This module is not part of the zero-dependency Python core and requires Bun to be installed.

Sources: src/repodocs/diagrams.py:L1-L20

Locating the diagram tool

tool_path() resolves tools/diagram_poster.ts relative to the repodocs source checkout (three parents up from diagrams.py) and returns None if that file doesn't exist — which is the case when running from an installed wheel that didn't ship the dev tool.

Sources: src/repodocs/diagrams.py:L23-L27

cmd_render_diagrams(repo, out) is the CLI entry point. It calls tool_path() and dies via die() if the tool is missing, and separately checks shutil.which("bun") to die with an install hint (https://bun.sh, then cd tools && bun install and bunx playwright install chromium) if Bun itself isn't on PATH.

Sources: src/repodocs/diagrams.py:L95-L102

Mermaid block extraction

Mermaid blocks are located with a single compiled regex, _MERMAID_RE = re.compile(r"```mermaid\n(.*?)```", re.DOTALL), matching non-greedily across newlines so multiple blocks in one page are each captured separately.

Sources: src/repodocs/diagrams.py:L20-L20

Per-page rendering flow

process_page(md, out, tool) reads the page, finds all mermaid blocks via _MERMAID_RE.finditer, and returns (0, 0) immediately if none are found. Otherwise it resolves a title with md_title(text) (falling back to md.stem), then for each block builds a poster YAML via _poster_yaml and attempts a render via _render_png, collecting either a PNG filename (f"{md.stem}-diagram-{i}.png") or None on failure into the pngs list.

A replacement pass then substitutes each match: if the corresponding pngs[k] entry is None, the original mermaid fence is returned unchanged ("leave the raw mermaid block"); otherwise it emits an image embed ![<caption>](<png>), where the caption is "{title} — diagram {k+1}" when there is more than one block, or "{title} diagram" for a single block. The function returns (rendered_count, failed_count).

Sources: src/repodocs/diagrams.py:L65-L92

Diagram Rendering diagram

Poster YAML generation

_poster_yaml(title, slug, mermaid, i, n) builds a minimal YAML document consumed by diagram_poster.ts. It sets a kicker of "REPODOCS · {slug.upper()}", appending " · DIAGRAM {i}/{n}" when there is more than one diagram on the page (n > 1). Text scalars (title, kicker, headline, footer right) are wrapped with json.dumps so titles containing quotes or colons remain valid YAML. The mermaid source itself is emitted as a literal block scalar (mermaid: |) with each line indented by two spaces.

Sources: src/repodocs/diagrams.py:L30-L43

Invoking the render tool

_render_png(tool, out, stem, i, yaml_text) writes the YAML to out/<stem>-diagram-<i>.yaml, then runs bun <tool> <yaml_path> --png via subprocess.run. It considers the render successful only if the process returncode is 0 and the expected PNG (<stem>-diagram-<i>.png) exists and is non-empty. On failure with stderr output, it prints the last line of stderr prefixed with the PNG's basename. The intermediate .yaml and .html files are always removed in a finally block, regardless of success.

Sources: src/repodocs/diagrams.py:L46-L62

Driving rendering across a page set

cmd_render_diagrams iterates every *.md file in out except index.md (sorted), calling process_page on each. It logs a per-page note ("{r} rendered", plus ", {f} failed" when applicable) via log() whenever a page had any rendered or failed blocks, accumulates totals, prints a final summary line, and returns exit code 1 if any diagram failed across all pages, 0 otherwise.

Sources: src/repodocs/diagrams.py:L95-L114

Failure handling

Failures are handled per-block, not per-page: a page can have some diagrams successfully replaced with image embeds while others remain as raw mermaid fences, since the replacement logic in process_page checks each block's pngs[k] entry independently. This is exercised directly in the test suite — test_process_page_keeps_block_on_render_failure stubs _render_png to always return False and asserts the mermaid fence stays in the output ("unrendered block stays honest").

Sources: tests/test_fix_diagrams.py:L23-L28, src/repodocs/diagrams.py:L83-L89

Test coverage

tests/test_fix_diagrams.py stubs dg._render_png via monkeypatch so the tests exercise process_page and _poster_yaml without needing Bun or Playwright installed:

Test Behavior verified
test_process_page_swaps_rendered_block A single successfully rendered block becomes ![Arch diagram](arch-diagram-1.png) and the mermaid fence is removed
test_process_page_keeps_block_on_render_failure A failed render leaves the raw ```mermaid fence in place
test_process_page_numbers_multiple_blocks Multiple blocks on one page are captioned "P — diagram 1" / "P — diagram 2" and numbered sequentially in filenames
test_poster_yaml_indents_mermaid_and_numbers_multi _poster_yaml emits "DIAGRAM 2/3" in the kicker and two-space-indents the mermaid literal block

Sources: tests/test_fix_diagrams.py:L1-L46

Clone this wiki locally