Rendered Fences Design

Shipped in 2.5.0 (2026-10-25). The repo spec is docs/superpowers/specs/2026-10-24-rendered-fences-design.md; this page is the as-built summary.

Second renderer: server-rendered PlantUML for UML class, activity and other @startuml diagrams shipped in 2.5.1 — see PlantUmlFencesDesign.

Why Mermaid fences never rendered before 2.5.0

Before 2.5.0 the production corpus held 63 ```mermaid fences on 43 pages, and none of them drew. The server's Flexmark setup turned GitLab's Mermaid rendering off, and the frontend had no diagram library, so every diagram showed as a plain code block. The content agent requires a diagram on every non-hub page, so the count kept growing.

The goal was to draw those diagrams for human readers, keep the source text for every machine consumer, and build the mechanism as a general hook: a rendered fence is any fenced block whose language a registered renderer claims. Mermaid was the first renderer and PlantUML the second; Vega-Lite is a possible next one.

How a rendered fence flows from markdown to a drawing

flowchart LR
  MD[Page markdown] --> Flex[Flexmark node renderer]
  Flex --> Policy{Server safety policy}
  Policy -->|allowed| Env[figure.wk-fence envelope with source]
  Policy -->|refused| Ref[envelope plus refusal reason]
  Env --> Browser{Browser policy and Mermaid}
  Browser -->|ok| SVG[Drawn SVG plus Show source]
  Browser -->|error| Err[Source plus parse error]
  MD --> Scan[RenderedFenceScanner]
  Scan --> Refs[ReferenceManager links]
  click Refs "/wiki/PageGraphVsKnowledgeGraph"

The source text always reaches the served HTML (SSR /wiki/{slug} and contentHtml). ?format=md, the for-agent projection, chunking, embeddings and export all carry raw markdown and are unchanged.

The info-string grammar and the example modifier

flowchart LR
  Draft --> Review
  click Review "/wiki/EditPageHelp"

The block above is written with the example modifier, so it shows its syntax instead of drawing.

The envelope shape and the two renderer kinds

Every rendered fence is emitted in one fixed shape: <figure class="wk-fence" data-fence data-fence-kind> wrapping <pre class="wk-fence-source"><code class="language-mermaid">, with the source HTML-escaped.

To add a renderer, read the RenderedFenceRenderer Javadoc in com.wikantik.markdown.extensions.fences and the header of wikantik-frontend/src/utils/fences/registry.js. Both are written as the how-to guide.

Fail-closed Mermaid safety policy, enforced twice

The policy refuses whole categories rather than mirroring Mermaid's lexers. Four review rounds showed that a deny-list of specific patterns never converges. A Mermaid fence is refused if it contains any of these:

HTML entities are decoded before link targets are checked, and the checks also run on statement fragments split at ;.

The same rules exist in Java (MermaidStatementPolicy, MermaidUrlRules, MermaidTextRules) and in JavaScript (mermaidPolicy.js), because the editor previews never call the server. One shared fixture, wikantik-frontend/src/utils/__fixtures__/renderedFences.json, drives both test suites, so the two cannot drift. A refused fence shows its source and the reason, never a blank space.

As a second layer, Mermaid runs at securityLevel: antiscript. Its secure key list covers dompurifyConfig and htmlLabels, and a hardened dompurifyConfig strips forms, inputs and inline styles from labels. In review, labels were found able to carry working off-site forms, which this closes.

A line click Node "/wiki/PageName" makes the node a link, and the page link counts as a real reference. RenderedFenceScanner is a raw-text scanner, so no Flexmark parse is needed. It finds rendered fences, including ~~~, indented, list-nested and blockquote-nested ones, skips example fences, and feeds each renderer's link extractor into DefaultReferenceManager.scanWikiLinks. Bumping the reference manager's cache version made 2.5.0's first startup rescan every page, which took 4.5 seconds on prod.

External http(s) targets are clickable but not tracked. Renaming a page does not yet rewrite diagram links. The rename would have to touch only the renderer's own link syntax, only inside rendered fences and never in example fences.

What readers and editors see

Corpus checking with check-mermaid.mjs

node wikantik-frontend/scripts/check-mermaid.mjs [--json] <files|dir|-> parses every diagram it finds. It exits 0 when all diagrams parse, 1 when some are broken, and 2 when the checker itself fails. The content agent's linter calls it and reports a broken diagram as a required fix. Before the release, the three prod diagrams that did not parse (RecommendationSystems, SimpleAgilityAttributionRFC, SimpleAgilityCommerceDataFlow) were repaired, and all 63 prod diagrams draw. PlantUML has the matching bin/check-plantuml.sh.

Deliberately out of scope

See WikantikDevelopmentHub for the rest of the platform designs.