From 5e5fedf22cb2a337149010475b104e00e93964d3 Mon Sep 17 00:00:00 2001 From: dongmucat <1127093059@qq.com> Date: Mon, 21 Sep 2026 09:59:17 +0800 Subject: [PATCH] fix(frontend): clean up failed mermaid renders Signed-off-by: dongmucat <1127093059@qq.com> --- web/e2e/markdown-mermaid.spec.ts | 2 ++ .../features/skill/markdown-renderer.test.tsx | 8 ++++++-- web/src/features/skill/markdown-renderer.tsx | 18 ++++++++++++++---- 3 files changed, 22 insertions(+), 6 deletions(-) diff --git a/web/e2e/markdown-mermaid.spec.ts b/web/e2e/markdown-mermaid.spec.ts index a95bd313..d4b71e34 100644 --- a/web/e2e/markdown-mermaid.spec.ts +++ b/web/e2e/markdown-mermaid.spec.ts @@ -55,9 +55,11 @@ test.describe('Markdown Mermaid rendering (Real API)', () => { await page.goto(`/space/${encodeURIComponent(namespace.slug)}/${encodeURIComponent(skill.slug)}`) + await expect(page.locator('[data-mermaid-error]')).toBeVisible() const source = page.locator('pre code.language-mermaid') await expect(source).toContainText('this is not a Mermaid diagram') await expect(page.locator('[data-mermaid-diagram]')).toHaveCount(0) + await expect(page.locator('body > div[id^="dmermaid-"]')).toHaveCount(0) } finally { await builder.cleanup() } diff --git a/web/src/features/skill/markdown-renderer.test.tsx b/web/src/features/skill/markdown-renderer.test.tsx index a23cb52b..9f596f5d 100644 --- a/web/src/features/skill/markdown-renderer.test.tsx +++ b/web/src/features/skill/markdown-renderer.test.tsx @@ -92,7 +92,11 @@ describe('MarkdownRenderer Mermaid blocks', () => { await waitFor(() => expect(container.querySelector('[data-testid="mermaid-svg"]')).toBeTruthy()) - expect(mermaidInitialize).toHaveBeenCalledWith({ startOnLoad: false, securityLevel: 'strict' }) + expect(mermaidInitialize).toHaveBeenCalledWith({ + startOnLoad: false, + securityLevel: 'strict', + suppressErrorRendering: true, + }) expect(container.querySelector('[data-testid="mermaid-svg"]')?.closest('pre')).toBeNull() }) @@ -103,7 +107,7 @@ describe('MarkdownRenderer Mermaid blocks', () => { , ) - await waitFor(() => expect(mermaidRender).toHaveBeenCalled()) + await waitFor(() => expect(container.querySelector('[data-mermaid-error]')).toBeTruthy()) expect(container.querySelector('pre code')?.textContent).toContain('not a valid diagram') }) diff --git a/web/src/features/skill/markdown-renderer.tsx b/web/src/features/skill/markdown-renderer.tsx index ddedbf4f..cab7a561 100644 --- a/web/src/features/skill/markdown-renderer.tsx +++ b/web/src/features/skill/markdown-renderer.tsx @@ -28,7 +28,11 @@ let mermaidBlockSequence = 0 function loadMermaid(): Promise { mermaidPromise ??= import('mermaid').then(({ default: mermaid }) => { - mermaid.initialize({ startOnLoad: false, securityLevel: 'strict' }) + mermaid.initialize({ + startOnLoad: false, + securityLevel: 'strict', + suppressErrorRendering: true, + }) return mermaid }) @@ -60,9 +64,12 @@ function getTextContent(node: ReactNode): string { .join('') } -function CodeBlock({ children }: { children: ReactNode }) { +function CodeBlock({ children, mermaidError }: { children: ReactNode; mermaidError?: boolean }) { return ( -
+
{children}
@@ -79,10 +86,12 @@ const MermaidBlock = memo(function MermaidBlock({ children }: MermaidBlockProps) const renderId = useRef(`mermaid-${++mermaidBlockSequence}`).current const diagramRef = useRef(null) const [svg, setSvg] = useState(null) + const [failed, setFailed] = useState(false) useEffect(() => { let mounted = true setSvg(null) + setFailed(false) enqueueMermaidRender(async () => { const mermaid = await loadMermaid() @@ -96,6 +105,7 @@ const MermaidBlock = memo(function MermaidBlock({ children }: MermaidBlockProps) .catch(() => { if (mounted) { setSvg(null) + setFailed(true) } }) @@ -111,7 +121,7 @@ const MermaidBlock = memo(function MermaidBlock({ children }: MermaidBlockProps) }, [svg]) if (!svg) { - return {children} + return {children} } return (