Skip to content

Fix multiple Mermaid/Graphviz diagrams not rendering#363

Merged
schuyler merged 3 commits into
mainfrom
claude/issue-331-mermaid-multiple-diagrams
Apr 9, 2026
Merged

Fix multiple Mermaid/Graphviz diagrams not rendering#363
schuyler merged 3 commits into
mainfrom
claude/issue-331-mermaid-multiple-diagrams

Conversation

@schuyler

@schuyler schuyler commented Apr 9, 2026

Copy link
Copy Markdown
Owner

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.

schuyler added 3 commits April 8, 2026 16:58
The rendering loops in mermaid.init.js and viz.init.js navigated two
levels up from the <code> element and set innerHTML on the shared
parent wrapper. This replaced the entire parent's content — including
all subsequent diagram <pre> elements — on the first iteration. Later
iterations found their elements detached with a null parentElement,
causing them to fail silently. Only the first diagram in any document
ever rendered.

Fix: replace only the specific <pre> element via outerHTML instead of
clobbering the shared parent's innerHTML. Each diagram's <pre> is
replaced in isolation, leaving sibling elements intact.

Also adds a tagName guard in mermaid.init.js to skip gracefully if the
DOM structure is ever unexpected.

Related to #331
Related to #332
Matches the defensive guard already present in mermaid.init.js.
If a <code> element is somehow detached or not inside a <pre>,
log a warning and skip rather than throw a TypeError.

Related to #332
The file was added as part of issue #332 work but was absent from
the test_coverage_improvement_plan.md tracking document.

Related to #331
@github-actions

github-actions Bot commented Apr 9, 2026

Copy link
Copy Markdown
Contributor

Code Coverage Report

Current Coverage: 58.04%

Coverage Details (Summary)
Name                                                                                                                                   Coverage            
-------------------------------------------------------------------------------------------------------------------------------------- ------------------- 
MASPreferences.bundle                                                                                                                  0.00% (0/0)         
MacDown 3000.app                                                                                                                       60.15% (8339/13863) 
    /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/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/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/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/MacDown/Code/Utility/MPResourceWatcherSet.m                                             93.62% (88/94)      
        -[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% (32/32)     
        __42-[MPResourceWatcherSet addWatcherForPath:]_block_invoke                                                                    100.00% (6/6)       
        __42-[MPResourceWatcherSet addWatcherForPath:]_block_invoke.37                                                                 93.33% (14/15)      
        __42-[MPResourceWatcherSet addWatcherForPath:]_block_invoke_2                                                                  75.00% (6/8)        
        -[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)      

... (2918 more lines truncated)

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

@schuyler

schuyler commented Apr 9, 2026

Copy link
Copy Markdown
Owner Author

Confidence Note

The root cause analysis is solid and the fix is logically correct, but I want to flag the limits of what we can verify here.

What we're confident about: querySelectorAll returns a static snapshot, so domAll[i] references remain valid across iterations. pre.outerHTML = result.svg replaces only the specific <pre>, leaving siblings intact. The rendering flag blocks MutationObserver re-entry during the async loop. The code path is deterministic and the bug was clearly present.

What we haven't verified:

  • No manual test run — the CI tests cover the Obj-C HTML generation pipeline, not the JavaScript rendering loop. The fix could be correct and still have an edge case we haven't seen.
  • Mermaid 11.x injects a temporary scratch element during render() for SVG dimension calculation. We've reasoned that the rendering guard covers any interaction with the MutationObserver, but that's inference, not observation.
  • The <pre><code class="language-mermaid"> DOM structure is confirmed by existing tests, but we haven't inspected the live WebView DOM to rule out any additional wrapping from Prism or the preview template.

Recommendation: Ship in v3000.0.6 and ask @rcuisnier and @gino-santerre-telus to confirm. If both diagrams now render, we're done. If not, the next debugging step is inspecting the live DOM structure in the WebView console.

@schuyler schuyler merged commit 58e824c into main Apr 9, 2026
10 checks passed
@schuyler schuyler deleted the claude/issue-331-mermaid-multiple-diagrams branch April 9, 2026 01:36
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.

1 participant