fix: isolate generated SVG figures as images

This commit is contained in:
amosblomqvist
2026-10-25 23:29:00 +02:00
parent fc3673466d
commit 6701503801
5 changed files with 282 additions and 50 deletions
+7 -16
View File
@@ -6,7 +6,7 @@
* 1. bridge-script injection (buildDemoSrcDoc) + the pure bridge-message
* reducer (reduceDemoBridgeState)
* 2. card rendering (static markup): sandboxed iframe attrs, error chip,
* failure notes, inline sanitized svg
* failure notes, SVG image documents
* 3. the demo-builder loop with a FAKE model + FAKE headless (success with
* a fix iteration, untested-final verification, never-clean failure,
* no-Chrome failure)
@@ -40,7 +40,7 @@ import {
INITIAL_DEMO_BRIDGE_STATE,
reduceDemoBridgeState,
} from "../components/artifacts/demo-card.tsx";
import { sanitizeSvg, svgNaturalWidth, VisualCard, VisualOutputCard } from "../components/artifacts/visual-card.tsx";
import { svgNaturalWidth, VisualCard, VisualOutputCard } from "../components/artifacts/visual-card.tsx";
import { slugFilename } from "../components/artifacts/download.ts";
import { chartSize } from "../components/session-overlay.tsx";
import { SubagentSteerCard } from "../components/artifacts/steer-card.tsx";
@@ -373,19 +373,19 @@ describe("steer card rendering (the delivered artifact)", () => {
assert.ok(!html.includes("<iframe"), "no iframe for a failed build");
});
it("renders the verified svg inline (sanitized); the maker's notes are glue and stay hidden", () => {
it("renders the verified svg as an image; the maker's notes are glue and stay hidden", () => {
const svg = '<svg xmlns="http://www.w3.org/2000/svg" width="200" height="100"><circle cx="50" cy="50" r="20"/></svg>';
const steer = buildSubagentSteer(
{ id: "jv", kind: "visual", toolCallId: "cv" },
{ svg, notes: "Looking at the render, the diagram is clean and accurate." },
);
const html = renderToStaticMarkup(<SubagentSteerCard message={projectNative([steer])[0] as never} />);
assert.ok(html.includes("circle"), "svg content rendered inline");
assert.ok(html.includes(encodeURIComponent(svg)), "the image URL contains the original SVG");
assert.match(html, /Figure/);
// Natural sizing (owner, 2026-10-07): a DECLARED width (200) means the
// holder hugs + centers rather than stretching full-width.
assert.match(html, /w-fit/, "holder hugs the declared size");
assert.match(html, /svg\]:max-w-full/, "svg shrinks to fit only when oversized");
assert.match(html, /class="block h-auto max-w-full"/, "image shrinks to fit only when oversized");
assert.ok(!html.includes("Looking at the render"), "model-facing notes never render");
});
@@ -590,17 +590,8 @@ describe("setSteerUiInverted (the persisted invert switch, 2026-10-07)", () => {
});
});
describe("sanitizeSvg", () => {
it("strips script blocks, event handlers, and javascript: urls, keeps geometry", () => {
const dirty = '<svg width="10" height="10" onload="alert(1)"><script>alert(2)</script><circle r="1" onclick="pwn()"/><a href="javascript:alert(3)"><rect width="2" height="2"/></a></svg>';
const clean = sanitizeSvg(dirty);
assert.ok(!/<script/i.test(clean), "script tags removed");
assert.ok(!/\sonload/i.test(clean), "onload removed");
assert.ok(!/\sonclick/i.test(clean), "onclick removed");
assert.ok(!/javascript:/i.test(clean), "javascript: urls removed");
assert.ok(clean.includes("circle") && clean.includes("rect"), "geometry preserved");
});
});
// Generated-figure image boundaries are covered in svg-image.test.tsx.
// There is no longer a partial regex sanitizer in the display path.
// ─── 3. the demo-builder loop ────────────────────────────────────────────────