Skip to content

Mermaid and chart specs are never validated at publish time, so broken diagrams ship as successful publishes #5

Description

@bitgorust

Summary

Component fences are validated at publish time and degrade to inline error boxes. Mermaid
and chart specs are not validated at all: they are inlined verbatim and fail in the
browser, after the page has been published and handed to a reader.

The result is that a publish can report success, and a chart-error scan of the output
HTML can come back clean, while the page is broken.

What happened

An agent session published an architecture page whose sequence diagram contained:

Note over S: builder holds AccountState; cut it at boundarySeq

Mermaid treats ; as a statement terminator in sequence diagrams, so the note ends early
and the remainder parses as a statement with no arrow. The reader saw:

Syntax error in text
mermaid version 11.16.1

The publish itself succeeded. Scanning the rendered HTML for class="chart-error" returned
zero, because the only occurrences are the CSS rule and the two runtime handlers that inject
the box client-side.

Why this is worth fixing here

skills/artifact-pages/SKILL.md lists as a pre-publish check:

  1. No error boxes survive to publish — fix and re-render first

For component fences that check is enforceable, because dist/components.js returns
errorBox(...) at publish time. For mermaid, vega-lite, vega, and echarts it cannot
be performed at all, since nothing evaluates those specs until a browser does. An agent
following the checklist literally will still ship a broken page.

It is checkable with what already ships

The package already depends on mermaid. Parsing a diagram headlessly needs a DOM, but works:

const dom = new JSDOM("<!doctype html><html><body></body></html>");
globalThis.window = dom.window; globalThis.document = dom.window.document;
globalThis.Element = dom.window.Element; globalThis.SVGElement = dom.window.SVGElement;
globalThis.DOMParser = dom.window.DOMParser; globalThis.Node = dom.window.Node;
const m = (await import("mermaid/dist/mermaid.core.mjs")).default;
m.initialize({ startOnLoad: false });
await m.parse(src);   // throws with the offending line and column

On the failing source above this reports:

Parse error on line 12 ... Expecting 'SOLID_ARROW', ... got 'NEWLINE'

Note that without a DOM the flowchart parser fails earlier with
DOMPurify.addHook is not a function, before its grammar is reached — so a naive headless
check silently validates only some diagram types. That trap is worth encoding once, here,
rather than in every consumer.

Vega-Lite has a cheaper path: vega-lite's compile() is already a build-time operation and
throws on an invalid spec without needing a DOM.

Suggested behaviour

  1. Validate mermaid and chart specs during publish, and emit the same inline error box the
    component fences already produce, so one rule covers every fence.
  2. If a jsdom dependency is unwanted in the default path, make it opt-in — a --validate-diagrams
    flag on render, or a validate: true argument on artifact_publish — and state in the
    skill's checklist that unvalidated diagrams are a known blind spot.
  3. Failing both, a cheap lint would have caught this specific case: a ; inside a sequence
    diagram Note line, and similar known-hazardous punctuation, are pure string checks.

Option 1 is the one that makes the existing checklist item true.

Environment

opencode-artifacts 0.14.4, mermaid 11.16.1, Node v24.19.0, OpenCode 1.18.9, macOS 25.5.0.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions