feat(markdown): mermaid diagram rendering (LC-550) #520

Merged
longjacksonle merged 2 commits from feat/LC-550-mermaid-diagrams into main 2026-07-07 19:50:03 +02:00

LC-550: Mermaid diagram rendering (Round 4, Vein C - content, quick win)

Render ```mermaid fenced code blocks as inline diagrams (flowcharts, sequence diagrams, etc.), the way LaTeX math already renders. Progressive enhancement: server emits a container, a vendored self-hosted mermaid bundle swaps in an SVG on the client. No CDN, no framework.

How it works

  • views/markdown.rs: a mermaid branch in the fenced-code path (next to the highlighter). A ```mermaid fence emits <div class="lc-mermaid" data-lc-mermaid><pre class="lc-mermaid-src">ESCAPED SOURCE</pre></div> instead of highlighted code. The info string is matched case-insensitively and tolerates trailing attributes.
  • assets/mermaid.js: a small framework-free loader that finds the containers and renders them. The ~3.5MB vendored bundle (assets/vendor/mermaid.min.js, mermaid 11.16.0) is fetched LAZILY, only once a page actually contains a diagram, so diagram-free pages pay nothing. New messages arriving over HTMX are re-scanned on htmx:afterSwap.

Safety and fallback

  • Diagram source arrives from other users' messages, so it is HTML-escaped server-side (cannot inject markup) and mermaid runs with securityLevel: 'strict' (labels sanitized, click/HTML disabled).
  • With JS off, the bundle failing to load, or a parse error, the escaped source <pre> stays visible as a readable fallback.
  • Theme-matched to the app's data-theme (dark/light). The existing CSP already permits a same-origin script plus mermaid's inline styles/eval, so no CSP change.

Caching / i18n

The container is deterministic from the source and carries no localized text, so it caches like any other rendered block (the content-keyed markdown cache stays correct across locales).

Tests

Unit (CI/lib): container shape, source escaping, case/attribute info-string variants, and that non-mermaid fences still highlight.

Notes

  • The vendored bundle is intentionally NOT in the service-worker precache (that would defeat the lazy load); it is fetched normally when first needed.
  • Re-theming already-rendered diagrams on a live theme toggle is deferred (they pick up the new theme on next load); kept out to hold the quick-win scope.

Gates: just check, full just test, just test-saas all green.

🤖 Generated with Claude Code

https://claude.ai/code/session_01S5obszLoUgUi8qaF7Hi5qz

## LC-550: Mermaid diagram rendering (Round 4, Vein C - content, quick win) Render ```mermaid fenced code blocks as inline diagrams (flowcharts, sequence diagrams, etc.), the way LaTeX math already renders. Progressive enhancement: server emits a container, a vendored self-hosted mermaid bundle swaps in an SVG on the client. No CDN, no framework. ### How it works - `views/markdown.rs`: a mermaid branch in the fenced-code path (next to the highlighter). A ```mermaid fence emits `<div class="lc-mermaid" data-lc-mermaid><pre class="lc-mermaid-src">ESCAPED SOURCE</pre></div>` instead of highlighted code. The info string is matched case-insensitively and tolerates trailing attributes. - `assets/mermaid.js`: a small framework-free loader that finds the containers and renders them. The ~3.5MB vendored bundle (`assets/vendor/mermaid.min.js`, mermaid 11.16.0) is fetched LAZILY, only once a page actually contains a diagram, so diagram-free pages pay nothing. New messages arriving over HTMX are re-scanned on `htmx:afterSwap`. ### Safety and fallback - Diagram source arrives from other users' messages, so it is HTML-escaped server-side (cannot inject markup) and mermaid runs with `securityLevel: 'strict'` (labels sanitized, click/HTML disabled). - With JS off, the bundle failing to load, or a parse error, the escaped source `<pre>` stays visible as a readable fallback. - Theme-matched to the app's `data-theme` (dark/light). The existing CSP already permits a same-origin script plus mermaid's inline styles/eval, so no CSP change. ### Caching / i18n The container is deterministic from the source and carries no localized text, so it caches like any other rendered block (the content-keyed markdown cache stays correct across locales). ### Tests Unit (CI/lib): container shape, source escaping, case/attribute info-string variants, and that non-mermaid fences still highlight. ### Notes - The vendored bundle is intentionally NOT in the service-worker precache (that would defeat the lazy load); it is fetched normally when first needed. - Re-theming already-rendered diagrams on a live theme toggle is deferred (they pick up the new theme on next load); kept out to hold the quick-win scope. Gates: `just check`, full `just test`, `just test-saas` all green. 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_01S5obszLoUgUi8qaF7Hi5qz
Slot a mermaid branch into the fenced-code path next to the existing highlighting: a ```mermaid fence (info string matched case-insensitively, attributes tolerated) emits an escaped-source container the client enhancement upgrades to an SVG, instead of syntax-highlighted code. The source is HTML-escaped so it cannot inject markup and round-trips through the loader's textContent read; the output is deterministic from the source, so it caches like any other block and carries no localized text. Unit tests cover the container shape, escaping, case/attribute variants, and that non-mermaid fences still highlight.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01S5obszLoUgUi8qaF7Hi5qz
feat(assets): vendored mermaid + lazy-loading diagram enhancement (LC-550)
All checks were successful
check-secrets / Nosey parker (push) Successful in 7s
check-secrets / Kingfisher (push) Successful in 8s
check-secrets / TruffleHog (push) Successful in 8s
check-secrets / TruffleHog (pull_request) Successful in 7s
check-secrets / Kingfisher (pull_request) Successful in 10s
Check / clippy + fmt + tests (pull_request) Successful in 6m30s
check-secrets / Nosey parker (pull_request) Successful in 8s
Create release / Create release from merged PR (pull_request) Has been skipped
e12696606d
Vendor mermaid 11.16.0 (self-hosted, no CDN) and add a small framework-free loader that finds [data-lc-mermaid] containers and swaps in an SVG. The ~3.5MB bundle is fetched lazily, only once a page actually contains a diagram, so diagram-free pages pay nothing. mermaid runs with securityLevel:'strict' (diagram source comes from other users' messages) and a theme matched to the app's data-theme; new messages arriving over HTMX are re-scanned on htmx:afterSwap. With JS off or the bundle/parse failing, the escaped source <pre> stays visible as a readable fallback. The CSP already allows a same-origin script plus mermaid's inline styles/eval.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01S5obszLoUgUi8qaF7Hi5qz
longjacksonle scheduled this pull request to auto merge when all checks succeed 2026-07-07 19:44:09 +02:00
longjacksonle deleted branch feat/LC-550-mermaid-diagrams 2026-07-07 19:50:04 +02:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
psa-systems/lets-chat!520
No description provided.