diff --git a/components/artifacts/svg-image.tsx b/components/artifacts/svg-image.tsx
new file mode 100644
index 0000000..6c74cdd
--- /dev/null
+++ b/components/artifacts/svg-image.tsx
@@ -0,0 +1,37 @@
+/** Two display-only repairs for inputs the old HTML embedding accepted.
+ * Do not rewrite stored artifacts/downloads or attempt general XML repair.
+ * Comments, CDATA and processing instructions are copied verbatim. */
+export function svgImageSource(svg: string): string {
+ let foundRoot = false;
+ return svg.replace(
+ /|$)||$)|<\?[\s\S]*?(?:\?>|$)|<\/?[a-zA-Z](?:[^<>"']|"[^"]*"|'[^']*')*>| /g,
+ (token) => {
+ if (token.startsWith("]]>');
+ assert.equal(svgImageSource(source), source);
+ });
+
+ it("adds only a missing root namespace, ignoring comments and attribute values", () => {
+ const source = '';
+ const expected = source.replace('Example`;
+ assert.equal(svgImageSource(source), source);
+ }
+ });
+
+ it("converts nbsp references in text and attributes, but not comments, CDATA or processing instructions", () => {
+ const source = 'A B ';
+ const expected = source.replace('aria-label="A B"', 'aria-label="A B"').replace('A B', 'A B');
+ assert.equal(svgImageSource(source), expected);
+ assert.equal(svgImageSource(expected), expected);
+ });
+});
+
+describe("generated SVG image boundary", () => {
+ for (const mode of ["inline", "fullscreen"] as const) {
+ for (const [name, source] of [["reported style rule and fragment IDs", FIGURE], ["active markup", ACTIVE_MARKUP]] as const) {
+ it(`${mode}: isolates ${name} inside the image URL`, () => {
+ const html = renderToStaticMarkup(mode === "inline"
+ ?
+ : {}} />);
+ const { document } = parseHTML(html);
+ const holder = document.querySelector(mode === "inline" ? '[data-testid="visual-svg"]' : "[data-lh-overlay-chart]")!;
+ assertImageBoundary(holder, source);
+ assert.ok(holder.classList.contains("bg-white"), "white backing retained");
+ });
+ }
+
+ for (const [name, original, display] of [
+ ["missing namespace", 'Example', 'Example'],
+ ["HTML entity", 'A B', 'A B'],
+ ] as const) {
+ it(`${mode}: repairs ${name} only in the image source`, () => {
+ const output = { svg: original };
+ const html = renderToStaticMarkup(mode === "inline"
+ ?
+ : {}} />);
+ const { document } = parseHTML(html);
+ const holder = document.querySelector(mode === "inline" ? '[data-testid="visual-svg"]' : "[data-lh-overlay-chart]")!;
+ assertImageBoundary(holder, display);
+ assert.equal(output.svg, original, "artifact source was not mutated");
+ });
+ }
+
+ it(`${mode}: retains inversion on the white backing`, () => {
+ const html = renderToStaticMarkup(mode === "inline"
+ ?
+ : {}} />);
+ const { document } = parseHTML(html);
+ const holder = document.querySelector(mode === "inline" ? '[data-testid="visual-svg"]' : "[data-lh-overlay-chart]")!;
+ assert.match(holder.getAttribute("style")!, /invert\(1\) hue-rotate\(180deg\)/);
+ assert.ok(holder.classList.contains("bg-white"));
+ assertImageBoundary(holder, FIGURE);
+ });
+ }
+
+ it("inline: retains natural width and only shrinks oversized figures", () => {
+ const { document } = parseHTML(renderToStaticMarkup());
+ const image = assertImageBoundary(document.querySelector('[data-testid="visual-svg"]')!, FIGURE);
+ assert.equal(image.getAttribute("width"), "320");
+ assert.equal(image.className, "block h-auto max-w-full");
+ assert.ok(document.querySelector('[data-testid="visual-card"]')!.classList.contains("w-fit"));
+ });
+
+ for (const width of ["", ' width="100%"']) {
+ it(`inline: keeps the full-width fallback for ${width || "viewBox-only"} SVG`, () => {
+ const source = `Example`;
+ const { document } = parseHTML(renderToStaticMarkup());
+ const image = assertImageBoundary(document.querySelector('[data-testid="visual-svg"]')!, source);
+ assert.equal(image.getAttribute("width"), null);
+ assert.equal(image.className, "block h-auto w-full");
+ assert.ok(document.querySelector('[data-testid="visual-card"]')!.classList.contains("w-full"));
+ });
+ }
+
+ it("fullscreen: keeps the natural-size box and zoom controls", () => {
+ const { document } = parseHTML(renderToStaticMarkup( {}} />));
+ const holder = document.querySelector("[data-lh-overlay-chart]")!;
+ assert.equal(assertImageBoundary(holder, FIGURE).className, "block h-auto w-full");
+ assert.match(holder.parentElement!.getAttribute("style")!, /width:320px/);
+ for (const label of ["Zoom in", "Zoom out", "Fit diagram to view", "Close"]) {
+ assert.ok(document.querySelector(`button[aria-label="${label}"]`));
+ }
+ });
+});
+
+it("the fullscreen button opens the same SVG image and inversion; download keeps original bytes", async () => {
+ const originalSvg = FIGURE.replace(' xmlns="http://www.w3.org/2000/svg"', '').replace("θ & φ", "θ & φ");
+ const displaySvg = FIGURE.replace("θ & φ", "θ & φ");
+ const dom = parseHTML("");
+ const names = ["window", "document", "Element", "HTMLElement", "IS_REACT_ACT_ENVIRONMENT"] as const;
+ const originals = names.map((name) => Object.getOwnPropertyDescriptor(globalThis, name));
+ Object.assign(globalThis, {
+ window: dom.window,
+ document: dom.document,
+ Element: dom.window.Element,
+ HTMLElement: dom.window.HTMLElement,
+ IS_REACT_ACT_ENVIRONMENT: true,
+ });
+ const host = dom.document.createElement("div");
+ dom.document.body.append(host);
+ const root = createRoot(host);
+ const originalCreateUrl = URL.createObjectURL;
+ const originalRevokeUrl = URL.revokeObjectURL;
+ const originalCreateElement = dom.document.createElement;
+ let downloaded: Blob | undefined;
+ let revoked: string | undefined;
+ let clicked: { name: string; href: string } | undefined;
+ URL.createObjectURL = (blob) => {
+ assert.ok(blob instanceof Blob);
+ downloaded = blob;
+ return "blob:figure-test";
+ };
+ URL.revokeObjectURL = (url) => { revoked = url; };
+ function Harness() {
+ const [focus, setFocus] = useState<{ svg: string; inverted: boolean } | null>(null);
+ return (
+ {}, openFigure: (svg, inverted) => setFocus({ svg, inverted }) }}>
+
+ {focus && setFocus(null)} />}
+
+ );
+ }
+ try {
+ await act(async () => root.render());
+ await act(async () => { host.querySelector('[data-testid="visual-open"]')!.dispatchEvent(new dom.window.Event("click", { bubbles: true })); });
+ const inline = assertImageBoundary(host.querySelector('[data-testid="visual-svg"]')!, displaySvg);
+ const overlayHolder = host.querySelector("[data-lh-overlay-chart]")!;
+ const fullscreen = assertImageBoundary(overlayHolder, displaySvg);
+ assert.equal(fullscreen.getAttribute("src"), inline.getAttribute("src"));
+ assert.match(overlayHolder.getAttribute("style")!, /invert\(1\) hue-rotate\(180deg\)/);
+
+ // Intercept only the download anchor; never navigate or write a real file.
+ dom.document.createElement = ((...args: Parameters) => {
+ const element = originalCreateElement.apply(dom.document, args);
+ if (args[0] === "a") {
+ element.click = () => { clicked = { name: element.getAttribute("download")!, href: element.getAttribute("href")! }; };
+ }
+ return element;
+ }) as typeof originalCreateElement;
+ await act(async () => { host.querySelector('[data-testid="visual-download"]')!.dispatchEvent(new dom.window.Event("click", { bubbles: true })); });
+ assert.ok(downloaded);
+ assert.equal(await downloaded.text(), originalSvg);
+ assert.equal(downloaded.type, "image/svg+xml");
+ assert.deepEqual(clicked, { name: "figure.svg", href: "blob:figure-test" });
+ assert.equal(revoked, "blob:figure-test");
+ } finally {
+ try {
+ await act(async () => root.unmount());
+ } finally {
+ URL.createObjectURL = originalCreateUrl;
+ URL.revokeObjectURL = originalRevokeUrl;
+ dom.document.createElement = originalCreateElement;
+ host.remove();
+ for (const [i, name] of names.entries()) {
+ if (originals[i]) Object.defineProperty(globalThis, name, originals[i]!);
+ else Reflect.deleteProperty(globalThis, name);
+ }
+ }
+ }
+});