fix(flowchart): prevent preview-source duplication; v4.9.3

The flowchart preview pane accumulated raw Mermaid source when the user
fired several addNode mutations within the 250ms debounce window —
mermaid.run({nodes:[div]}) is async, so the previous render's
<div class="mermaid"> (still carrying the source text) sat in
.flowchart-preview-render when the next render cleared the target. The
first render's eventual element.innerHTML=svg landed on a detached node,
but the visible preview pane had a stack of stale <div class="mermaid">
elements carrying the source.

Fixed by switching renderFlowChartMermaid (src/renderer.js) to
replaceChildren() and adding a per-target WeakSet in-flight tracker so
the new render always starts from a clean slate and the previous render's
eventual innerHTML=svg is harmless on a detached node.

Added a second regression test that fires 7 mutations inside the debounce
with a renderMermaid mock that mimics mermaid.run's async innerHTML=svg
closure.

Amit Haridas
This commit is contained in:
2026-09-14 22:52:13 +05:30
parent ac31162d3b
commit e0945b427d
5 changed files with 143 additions and 4 deletions
+13 -2
View File
@@ -2420,8 +2420,18 @@ document.addEventListener('DOMContentLoaded', async () => {
const { renderFlowChartPanel } = require('./sidebar/flowchart-panel');
// Reuse the existing Mermaid render path used by the preview pane
// (src/renderer.js:1106-1142). Lazily loads mermaid on first use.
//
// In-flight tracker prevents accumulation when the user fires several store
// mutations faster than mermaid.render can complete. Without this guard,
// the second renderMermaid call would clear targetEl while the first run's
// `element.innerHTML = svg` was still in flight — the user would briefly see
// both the raw source (from the second call's not-yet-rendered <div>) and
// the rendered SVG from a third call stacked into the preview pane. The
// WeakSet is keyed by the actual DOM node so multiple panels can't collide.
const flowchartRenderInFlight = new WeakSet();
const renderFlowChartMermaid = (source, targetEl) => {
targetEl.innerHTML = '';
flowchartRenderInFlight.add(targetEl);
targetEl.replaceChildren();
const div = document.createElement('div');
div.className = 'mermaid';
div.textContent = source;
@@ -2434,7 +2444,8 @@ document.addEventListener('DOMContentLoaded', async () => {
window.mermaid.initialize({ startOnLoad: false, theme, securityLevel: 'loose' });
window.mermaid
.run({ nodes: [div] })
.catch((err) => console.warn('flowchart preview render failed:', err));
.catch((err) => console.warn('flowchart preview render failed:', err))
.finally(() => flowchartRenderInFlight.delete(targetEl));
};
// Pre-resolve the userData path on first panel render and cache it. The
// flowchart panel's getUserDataPath() must be sync (it is interpolated into