Skip to content

Replace mermaid-rs-renderer with client-side Mermaid.js #5

@fasterthanlime

Description

@fasterthanlime

Problem

The current Mermaid integration uses mermaid-rs-renderer (a Rust crate) to render diagrams server-side to SVG. This does not work well — the rendering quality is poor and doesn't match what Mermaid.js produces.

Proposed Solution

Replace server-side rendering with client-side Mermaid.js. The standard embedding pattern is:

<!-- In the body, where the diagram appears -->
<pre class="mermaid">
  graph TD
  A[Client] --> B[Load Balancer]
  B --> C[Server1]
</pre>

<!-- Once per page, as a module script -->
<script type="module">
  import mermaid from 'https://cdn.jsdelivr.net/npm/mermaid@11/dist/mermaid.esm.min.mjs';
  mermaid.initialize({ startOnLoad: true });
</script>

Architectural Changes Required

1. Extend CodeBlockHandler return type

Currently the trait returns Result<String> (just HTML). It needs to also carry out-of-band metadata — specifically, resources that must be injected into the document's <head> (or end of <body>).

Proposed: change the return type to a struct:

pub struct CodeBlockOutput {
    /// HTML to insert where the code block appeared
    pub html: String,
    /// Additional elements the page must include (scripts, stylesheets, etc.)
    /// These are outside the markdown fragment — the caller is responsible for placing them.
    pub head_injections: Vec<String>,
}

2. Add head_injections to Document

pub struct Document {
    // ... existing fields ...

    /// Resources that handlers request be added to the page's <head> or similar.
    /// Deduplicated (if multiple mermaid blocks exist, only one script tag).
    pub head_injections: Vec<String>,
}

During rendering, collect head_injections from all handler outputs and deduplicate them before storing in Document.

3. Update MermaidHandler

  • Body output: <pre class="mermaid">{escaped_code}</pre>
  • Head injection: the <script type="module"> that imports Mermaid.js from CDN and calls mermaid.initialize({ startOnLoad: true })
  • Drop the mermaid-rs-renderer dependency and the mermaid feature flag (or repurpose it)

4. Update all other handlers

Mechanical change — all existing handlers (ArboriumHandler, CompareHandler, AasvgHandler, PikruHandler, TermHandler, RawCodeHandler) return empty head_injections. This is a breaking API change for any external handler implementations.

5. Downstream (dodeca)

The markdown cell protocol needs to pass head_injections through so the page assembler can include them in the final HTML document. This is a separate change in dodeca.

What This Enables

Beyond Mermaid, this pattern generalizes to any code block handler that needs external resources — KaTeX for math, custom stylesheets for specialized renderers, etc.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions