From 684eb9c7bc4462f43c08a299b1d657f3b50baee0 Mon Sep 17 00:00:00 2001 From: cliffhall Date: Thu, 27 Aug 2026 06:26:23 -0400 Subject: [PATCH] fix(web): reject an underflowing literal, and route embedded JSON resources MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Copilot's thirteenth pass, two comments, both real. - `1e-400` has a nonzero mantissa and parses to `0`. The digit comparison skips it for being exponent-form and it is not negative zero, so nothing saw a document that says one number and sends none of it. Caught now by pairing `Number(literal) === 0` with a nonzero digit in the mantissa, which leaves a literal written as zero (`0e-400`) and a subnormal that still parses to something (`1e-320`) alone. - The `resource` content-block branch rendered every embedded resource in a plain code block, ignoring `resource.mimeType`, so a prompt message carrying JSON did not inherit this migration even though the text-block branch did. An embedded *text* resource declaring JSON now goes through the same dispatch. Narrowed to JSON, as the Network tab's bodies are: routing every declared type through would also put an embedded `text/html` resource into the sandboxed frame and a `text/csv` one into a table. The Resources screen already renders resource contents that way, so doing it here too is defensible — but it is a change to what a tool result *is*, and belongs to whoever decides that. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01UL9retfvAXRgvi6EWk4SY4 Signed-off-by: cliffhall --- .../ContentViewer/ContentViewer.test.tsx | 33 +++++++++++++++++++ .../elements/ContentViewer/ContentViewer.tsx | 30 ++++++++++++++--- clients/web/src/utils/jsonObjectDraft.test.ts | 17 ++++++++++ clients/web/src/utils/jsonObjectDraft.ts | 23 +++++++++++-- 4 files changed, 96 insertions(+), 7 deletions(-) diff --git a/clients/web/src/components/elements/ContentViewer/ContentViewer.test.tsx b/clients/web/src/components/elements/ContentViewer/ContentViewer.test.tsx index bfd65add..66d5ace3 100644 --- a/clients/web/src/components/elements/ContentViewer/ContentViewer.test.tsx +++ b/clients/web/src/components/elements/ContentViewer/ContentViewer.test.tsx @@ -263,6 +263,39 @@ describe("ContentViewer", () => { expect(screen.getByRole("button", { name: "Copy" })).toBeInTheDocument(); }); + // The `resource` block branch used to render every embedded resource in a + // plain code block, so a prompt message carrying JSON did not inherit this + // migration even though the text-block branch did. + it("renders an embedded JSON resource in the JSON editor", () => { + const block: ContentBlock = { + type: "resource", + resource: { + uri: "file:///a.json", + mimeType: "application/json", + text: '{"a":1}', + }, + }; + renderWithMantine(); + expect(getAceText()).toBe('{\n "a": 1\n}'); + }); + + // Narrowed to JSON on purpose: routing every declared type through would put + // an embedded `text/html` resource into the sandboxed frame, which is a + // change to what a tool result is. + it("leaves a non-JSON embedded resource on the plain renderer", () => { + const block: ContentBlock = { + type: "resource", + resource: { + uri: "file:///a.html", + mimeType: "text/html", + text: "

hi

", + }, + }; + renderWithMantine(); + expect(screen.getByText("

hi

")).toBeInTheDocument(); + expect(document.querySelector("iframe")).toBeNull(); + }); + it("renders nothing when neither block nor contents is provided", () => { const { container } = renderWithMantine(); // MantineProvider injects a