diff --git a/packages/webui/src/client/features/transcript/ui/webui-mermaid.tsx b/packages/webui/src/client/features/transcript/ui/webui-mermaid.tsx index d5fbfa0b7..28d5f2231 100644 --- a/packages/webui/src/client/features/transcript/ui/webui-mermaid.tsx +++ b/packages/webui/src/client/features/transcript/ui/webui-mermaid.tsx @@ -26,8 +26,9 @@ // runs effects, so the block carries each state as a plain prop and the // wrapper only wires the async render into it. // 4. The rendered SVG is sanitized before it is injected, and the diagram is -// drawn with the theme the app is actually showing. Both are load-bearing -// — see the two sections below. +// drawn with the theme the app is actually showing — and redrawn when +// that theme changes, because the palette is baked into the SVG. Both +// are load-bearing — see the two sections below. import { useEffect, useState, type ReactElement } from "react"; @@ -101,6 +102,27 @@ export function readWebuiDocumentTheme( * body, not assembled from user HTML, so the surface is small — but small is * not zero, and the filter is what makes "the SVG carries no script" a * property of this component rather than a property of the library version. + * + * What is deliberately kept: the `", + ].join(""); + const clean = sanitizeWebuiMermaidSvg(styled); + expect(clean).not.toContain("@import"); + expect(clean).not.toContain("evil.example"); + expect(clean).toContain(".edge { fill: red; }"); + expect(clean).toContain("url(#arrow)"); + expect(clean).toContain("data:image/svg+xml;base64,PHN2Zw=="); + }); + it("leaves an ordinary diagram untouched", () => { const plain = '开始'; expect(sanitizeWebuiMermaidSvg(plain)).toBe(plain);