fix(ui): decode artifact previews as UTF-8 (#117657)

Co-authored-by: Peter Steinberger <steipete@macos.shared>
This commit is contained in:
Peter Steinberger
2026-08-01 19:05:31 -07:00
committed by GitHub
parent 1becbdcb60
commit 12a8d80873
2 changed files with 98 additions and 11 deletions
@@ -62,6 +62,95 @@ describe("custodian panel toggle", () => {
});
});
describe("session workspace artifacts", () => {
function createArtifactHost(params: { data: string; mimeType: string; title?: string }) {
const handleOpenSidebar = vi.fn();
const request = vi.fn().mockResolvedValue({
artifact: {
id: "artifact-1",
mimeType: params.mimeType,
title: params.title ?? "Unicode artifact",
},
data: params.data,
encoding: "base64",
});
const state = {
client: { request },
connected: true,
handleOpenSidebar,
hello: gatewayHello([]),
sessionKey: "agent:main:current",
sessions: {},
} as unknown as SessionWorkspaceHost;
return { handleOpenSidebar, request, state };
}
it.each([
{
content: "Résumé 東京 🦀",
fence: "```",
mimeType: "text/plain",
},
{
content: JSON.stringify({ message: "Résumé 東京 🦀" }),
fence: "```json",
mimeType: "application/json",
},
])(
"decodes UTF-8 $mimeType artifacts without corrupting visible or raw text",
async (testCase) => {
const data = btoa(String.fromCharCode(...new TextEncoder().encode(testCase.content)));
const { handleOpenSidebar, state } = createArtifactHost({
data,
mimeType: testCase.mimeType,
});
createSessionWorkspaceProps(state).onOpenArtifact("artifact-1");
await vi.waitFor(() => expect(handleOpenSidebar).toHaveBeenCalledOnce());
expect(handleOpenSidebar.mock.calls[0]?.[0]).toEqual({
kind: "markdown",
content: `# Unicode artifact\n\n${testCase.fence}\n${testCase.content}\n\`\`\``,
rawText: testCase.content,
});
},
);
it("preserves inline image artifacts as their original base64 data URLs", async () => {
const data = "iVBORw0KGgo=";
const { handleOpenSidebar, state } = createArtifactHost({
data,
mimeType: "image/png",
title: "preview.png",
});
createSessionWorkspaceProps(state).onOpenArtifact("artifact-1");
await vi.waitFor(() => expect(handleOpenSidebar).toHaveBeenCalledOnce());
expect(handleOpenSidebar.mock.calls[0]?.[0]).toEqual({
kind: "image",
mimeType: "image/png",
rawText: null,
src: `data:image/png;base64,${data}`,
title: "preview.png",
});
});
it("reports malformed base64 artifact data as a visible workspace error", async () => {
const { handleOpenSidebar, state } = createArtifactHost({
data: "not-base64!",
mimeType: "text/plain",
});
createSessionWorkspaceProps(state).onOpenArtifact("artifact-1");
await vi.waitFor(() =>
expect(createSessionWorkspaceProps(state).error).toMatch(/InvalidCharacterError|invalid/i),
);
expect(handleOpenSidebar).not.toHaveBeenCalled();
});
});
describe("openSessionWorkspaceFile", () => {
it("opens Markdown with a canonical Gateway- and pane-scoped draft identity", async () => {
const handleOpenSidebar = vi.fn();
@@ -281,19 +281,17 @@ function artifactSidebarContent(params: {
rawText: url ?? null,
};
}
if (encoding === "base64" && data && mimeType === "application/json") {
const decoded = globalThis.atob(data);
if (
encoding === "base64" &&
data &&
(mimeType === "application/json" || mimeType.startsWith("text/"))
) {
const bytes = Uint8Array.from(globalThis.atob(data), (char) => char.charCodeAt(0));
const decoded = new TextDecoder().decode(bytes);
const language = mimeType === "application/json" ? "json" : "";
return {
kind: "markdown",
content: `# ${title}\n\n\`\`\`json\n${decoded}\n\`\`\``,
rawText: decoded,
};
}
if (encoding === "base64" && data && mimeType.startsWith("text/")) {
const decoded = globalThis.atob(data);
return {
kind: "markdown",
content: `# ${title}\n\n\`\`\`\n${decoded}\n\`\`\``,
content: `# ${title}\n\n\`\`\`${language}\n${decoded}\n\`\`\``,
rawText: decoded,
};
}