Skip to content

Address issue #331: Mermaid rendering issue#333

Merged
schuyler merged 5 commits into
mainfrom
claude/resolve-issue-331-OZEOv
Feb 28, 2026
Merged

Address issue #331: Mermaid rendering issue#333
schuyler merged 5 commits into
mainfrom
claude/resolve-issue-331-OZEOv

Conversation

@schuyler

Copy link
Copy Markdown
Owner

Summary

  • Fix Mermaid diagrams not rendering during live preview (DOM replacement path) by adding a MutationObserver in mermaid.init.js that detects new .language-mermaid elements and triggers rendering automatically
  • Fix Mermaid diagrams breaking on full page reload (theme switch) by ensuring proper unique ID generation and adding a rendering guard against re-entrant calls
  • Add full-width CSS for rendered Mermaid SVGs so diagrams use the full preview pane width
  • Add 17 tests in MPMermaidRenderingTests.m covering the Obj-C rendering pipeline

Related Issue

Related to #331
Also filed #332 for the same bug in Graphviz (same root cause, separate fix)

Changes

File Description
MacDown/Resources/Extensions/mermaid.init.js Add MutationObserver + rendering guard inside existing IIFE
MacDown/Resources/Extensions/export.css Add svg[id^="mermaid_"] CSS rule for full-width diagrams
MacDownTests/MPMermaidRenderingTests.m New: 17 tests for mermaid rendering pipeline
MacDown 3000.xcodeproj/project.pbxproj Add new test file to Xcode project
plans/test_coverage_improvement_plan.md Document new test coverage
plans/xcuitest.md Document new test coverage

Root Cause

The preview has two rendering paths in MPDocument.m:

  1. DOM replacement (common path) — replaces body.innerHTML and re-triggers Prism and MathJax, but never re-triggered Mermaid rendering
  2. Full HTML reload (theme switch) — reloads entire page, which re-runs scripts

The mermaid.init.js script only ran on window.load, which only fires on full page reload. The MutationObserver now detects when .language-mermaid elements appear in the DOM after body.innerHTML replacement and triggers rendering automatically. After Mermaid renders, those elements are replaced with SVGs, providing a natural guard against infinite loops.

Manual Testing Plan

  1. Initial load: Open assets/demo.md — all 3 Mermaid diagrams (flow chart, sequence, Gantt) should render
  2. Live editing: Modify Mermaid source in editor — preview updates with re-rendered diagrams
  3. Theme switch: Change CSS theme in Preferences — all diagrams re-render (not just the first)
  4. Multiple theme switches: Switch themes 3-4 times rapidly — final state renders all diagrams
  5. Full-width: Resize window — Mermaid SVGs scale to fill preview pane width
  6. Syntax errors: Invalid Mermaid syntax shows red error box, other diagrams still render
  7. No Mermaid content: Documents without Mermaid blocks render normally with no performance impact
  8. Mermaid disabled: Unchecking Mermaid in Preferences shows raw code blocks

Review Notes

Add MPMermaidRenderingTests.m with 18 tests covering the Obj-C rendering
pipeline for Mermaid diagrams: code block class generation, script
inclusion/exclusion logic, preference change detection, multiple diagrams,
edge cases, and coexistence with MathJax and Graphviz.

These tests are written first (TDD) before implementing the fix for
Mermaid diagrams not re-rendering on DOM replacement.

Related to #331
- Add MutationObserver in mermaid.init.js to re-render diagrams when
  body.innerHTML is replaced (the DOM replacement path in MPDocument.m).
  Previously, Mermaid only rendered on window.load, so diagrams would not
  appear during live preview updates.

- Add rendering guard to prevent re-entrant calls when the observer fires
  during an active render cycle.

- Add early return when no .language-mermaid elements are found to avoid
  unnecessary work on every DOM mutation.

- Add CSS rule in export.css for full-width Mermaid SVG rendering, so
  diagrams use the full preview pane width instead of being constrained.

- Remove out-of-scope testExportCSSIncludedWithStyles test per review.

Related to #331
Script inclusion tests need MPAssetFullLink (render method) to check
for filenames like mermaid.min.js. The export path uses MPAssetEmbedded
which inlines script content without filenames.

Related to #331
Update test_coverage_improvement_plan.md and xcuitest.md to include
the new Mermaid rendering test file (17 tests for issue #331).

Related to #331
@github-actions

github-actions Bot commented Feb 27, 2026

Copy link
Copy Markdown
Contributor

Code Coverage Report

Current Coverage: 57.18%

Coverage Details (Summary)
Name                                                                                                                                   Coverage            
-------------------------------------------------------------------------------------------------------------------------------------- ------------------- 
MASPreferences.bundle                                                                                                                  0.00% (0/0)         
MacDown 3000.app                                                                                                                       59.86% (8213/13720) 
    /Users/runner/work/macdown3000/macdown3000/MacDown/Code/Preferences/MPHtmlPreferencesViewController.m                              0.00% (0/112)       
        MPPrismDefaultThemeName                                                                                                        0.00% (0/2)         
        -[MPHtmlPreferencesViewController viewIdentifier]                                                                              0.00% (0/3)         
        -[MPHtmlPreferencesViewController toolbarItemImage]                                                                            0.00% (0/3)         
        -[MPHtmlPreferencesViewController toolbarItemLabel]                                                                            0.00% (0/2)         
        -[MPHtmlPreferencesViewController viewWillAppear]                                                                              0.00% (0/4)         
        -[MPHtmlPreferencesViewController changeStylesheet:]                                                                           0.00% (0/7)         
        -[MPHtmlPreferencesViewController changeHighlightingTheme:]                                                                    0.00% (0/7)         
        -[MPHtmlPreferencesViewController invokeStylesheetFunction:]                                                                   0.00% (0/23)        
        -[MPHtmlPreferencesViewController invokeHighlightingThemeFunction:]                                                            0.00% (0/33)        
        -[MPHtmlPreferencesViewController loadStylesheets]                                                                             0.00% (0/13)        
        -[MPHtmlPreferencesViewController loadHighlightingThemes]                                                                      0.00% (0/15)        
    /Users/runner/work/macdown3000/macdown3000/MacDown/Code/Extension/NSColor+HTML.m                                                   94.05% (332/353)    
        +[NSColor(HTML) colorWithHexString:]                                                                                           94.12% (16/17)      
        +[NSColor(HTML) colorWithHTMLName:]                                                                                            89.13% (164/184)    
        __35+[NSColor(HTML) colorWithHTMLName:]_block_invoke                                                                           100.00% (152/152)   
    /Users/runner/work/macdown3000/macdown3000/MacDown/Code/Utility/FileURLInlining.m                                                  26.60% (25/94)      
        +[FileURLInlining inlineFromIterable:]                                                                                         0.00% (0/11)        
        -[FileURLInlining initWithURL:]                                                                                                0.00% (0/6)         
        -[FileURLInlining init]                                                                                                        100.00% (3/3)       
        -[FileURLInlining inlineContent]                                                                                               0.00% (0/9)         
        -[FileURLInlining imageContent]                                                                                                0.00% (0/12)        
        +[FileURLInlining mimeTypeForFilePath:]                                                                                        100.00% (22/22)     
        -[FileURLInlining clippingContent]                                                                                             0.00% (0/4)         
        +[FileURLInlining isTextClippingAtPath:]                                                                                       0.00% (0/12)        
        +[FileURLInlining textContentFromClipping:]                                                                                    0.00% (0/15)        
    /Users/runner/work/macdown3000/macdown3000/Dependency/peg-markdown-highlight/HGMarkdownHighlightingStyle.m                         87.69% (57/65)      
        +[HGMarkdownHighlightingStyle colorFromARGBColor:]                                                                             100.00% (6/6)       
        -[HGMarkdownHighlightingStyle initWithType:attributesToAdd:toRemove:fontTraitsToAdd:]                                          88.89% (8/9)        
        -[HGMarkdownHighlightingStyle initWithStyleAttributes:baseFont:]                                                               86.00% (43/50)      
    /Users/runner/work/macdown3000/macdown3000/MacDown/Code/Utility/MPResourceWatcherSet.m                                             87.10% (54/62)      
        -[MPResourceWatcherSet init]                                                                                                   100.00% (6/6)       
        -[MPResourceWatcherSet dealloc]                                                                                                0.00% (0/3)         
        -[MPResourceWatcherSet watchedPaths]                                                                                           100.00% (3/3)       
        -[MPResourceWatcherSet updateWatchedPaths:]                                                                                    100.00% (16/16)     
        -[MPResourceWatcherSet addWatcherForPath:]                                                                                     100.00% (18/18)     
        __42-[MPResourceWatcherSet addWatcherForPath:]_block_invoke                                                                    100.00% (6/6)       
        __42-[MPResourceWatcherSet addWatcherForPath:]_block_invoke.5                                                                  0.00% (0/5)         
        -[MPResourceWatcherSet stopAll]                                                                                                100.00% (5/5)       
    /Users/runner/work/macdown3000/macdown3000/MacDown/Code/Extension/hoedown_html_patch.c                                             86.47% (115/133)    
        hoedown_patch_reset_checkbox_index                                                                                             100.00% (3/3)       
        hoedown_patch_get_checkbox_index                                                                                               0.00% (0/3)         
        hoedown_patch_render_blockcode                                                                                                 89.09% (49/55)      
        hoedown_patch_render_listitem                                                                                                  100.00% (44/44)     
        hoedown_patch_render_toc_header                                                                                                67.86% (19/28)      
    /Users/runner/work/macdown3000/macdown3000/MacDown/Code/Application/MPMainController.m                                             41.78% (94/225)     

... (2856 more lines truncated)

📊 **Full coverage report available in workflow artifacts**

@schuyler schuyler merged commit 0a8b64e into main Feb 28, 2026
10 checks passed
@schuyler schuyler deleted the claude/resolve-issue-331-OZEOv branch February 28, 2026 00:43
schuyler added a commit that referenced this pull request Apr 8, 2026
Related to #332

window.load doesn't fire when MPDocument.m replaces body.innerHTML
during live editing, so Graphviz diagrams never rendered after the
initial page load. This is the same class of bug fixed for Mermaid
in #331 / PR #333.

Changes:
- viz.init.js: add rendering guard flag and MutationObserver that
  watches for .language-{engine} elements across all 6 Graphviz
  engines (circo, dot, fdp, neato, osage, twopi); calls init() when
  any are found. Loop prevention is natural: doGraphviz() replaces
  the code element with SVG, so the trigger element disappears before
  the observer can fire again.
- MPGraphvizRenderingTests.m: new test file mirroring
  MPMermaidRenderingTests.m; covers code block rendering, script
  inclusion/exclusion, preference change detection, multiple diagrams,
  mixed content, edge cases, and graphviz+mermaid coexistence.
- project.pbxproj: register MPGraphvizRenderingTests.m in
  MacDownTests target.
schuyler added a commit that referenced this pull request Apr 8, 2026
…ng (#357)

## Summary

- `viz.init.js`: adds a `rendering` guard flag and a `MutationObserver`
that watches for `.language-{engine}` elements across all 6 Graphviz
engines; calls `init()` when any are found after `body.innerHTML`
replacement. Loop prevention is structural — `doGraphviz()` replaces the
triggering `<code>` element with SVG before the observer can fire again.
- `MPGraphvizRenderingTests.m`: new test file mirroring
`MPMermaidRenderingTests.m`; 15 tests covering code block rendering,
script inclusion/exclusion, preference change detection, style changes,
multiple diagrams, mixed content, edge cases, and graphviz+mermaid
coexistence.
- `project.pbxproj`: registers new test file in the `MacDownTests`
target.

This is the same class of bug fixed for Mermaid in #331 / PR #333.

## Test plan

- [x] All four CI runners passed (macos-14, macos-15, macos-15-intel,
macos-26)
- [ ] Manual: open a doc with a `dot` fence, edit it — diagram
re-renders without full reload

Related to #332
schuyler added a commit that referenced this pull request Apr 9, 2026
## Summary

- Fix `mermaid.init.js`: the rendering loop navigated to the *parent* of
each `<pre>` and set its `innerHTML`, replacing the entire parent's
content on the first iteration and destroying all subsequent diagram
elements. Changed to `pre.outerHTML = result.svg` to replace only the
specific `<pre>`. Added null/tagName guard for unexpected DOM
structures.
- Fix `viz.init.js`: identical root cause
(`dom.parentElement.parentElement.innerHTML`). Changed to
`dom.parentElement.outerHTML`. Added matching null/tagName guard.
- Update `plans/test_coverage_improvement_plan.md`:
`MPGraphvizRenderingTests.m` was missing from the tracking document.

## Root Cause

PR #333 (v3000.0.5) fixed the MutationObserver path so Mermaid renders
at all on DOM replacement, but the rendering loop itself was broken
regardless of how it was triggered. `querySelectorAll` returns a static
snapshot, so the loop correctly collects all elements — but the first
iteration's `innerHTML` assignment on the shared parent element removed
all siblings from the DOM, leaving subsequent elements with
`parentElement === null` and silently failing.

## Related Issues

Related to #331
Related to #332

## Manual Testing Plan

13 scenarios covering: multiple Mermaid diagrams, multiple Graphviz
diagrams, mixed engines, invalid syntax alongside valid diagrams,
live-edit re-render, and Mermaid+Graphviz together in one document. Full
checklist:

- M-1: Two Mermaid diagrams render (primary regression)
- M-2: Three Mermaid diagrams render
- M-3: Single Mermaid diagram (no regression)
- M-4: Invalid Mermaid + valid Mermaid — error in first doesn't block
second
- M-5: Live-edit re-render with multiple Mermaid diagrams
- M-6: Mermaid mixed with tables, code blocks, other content
- G-1: Two Graphviz diagrams render (primary regression)
- G-2: Three Graphviz diagrams render
- G-3: Mixed Graphviz engines (dot, neato, circo) in one document
- G-4: Invalid Graphviz + valid Graphviz — error doesn't block second
- G-5: Live-edit re-render with multiple Graphviz diagrams
- C-1: Mermaid and Graphviz together in one document
- C-2: HTML export with multiple diagrams

## Review Notes

No Obj-C changes. No automated JS tests possible (no WKWebView test
harness exists in this project). The fix is a mechanical change to DOM
replacement strategy; the `rendering` guard, MutationObserver, and
unique ID scheme are unchanged.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants