authorgravatar for saiansh2525@gmail.comYash Singh <saiansh2525@gmail.com> 2026-10-12 15:30:03-05:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2026-10-12 15:30:03-05:00
log511d20c6f20c2ed6ff787aba068d7d673e2949b9
tree905a01943f719acb0a1dbb6c28e725c05004f139
parent0e7346ccd9427c06161e222319e718e6bd70887f
signature Signed by PGP key B5690EEEBB952194

fix(mobile): hide Claude Read image placeholders when previews exist (#18761)


3 files changed, 47 insertions(+), 4 deletions(-)

apps/mobile/src/features/threads/thread-work-log.tsx+3-2
......@@ -962,12 +962,14 @@ const ThreadWorkLogRow = memo(function ThreadWorkLogRow(
962962 : row.getFullDetail()
963963 : null;
964964 const outputImages = expanded && fetchedItem ? turnItemOutputImages(fetchedItem) : [];
965 const viewedImagePath = workEntryViewedImagePath(row.workEntry);
965966 const fetchedOutput = !expanded
966967 ? null
967968 : shownItem.type === "file_search" || shownItem.type === "web_search"
968969 ? turnItemOutputText(shownItem)
969970 : fetchedItem
970 ? (turnItemOutputText(fetchedItem) ?? (outputImages.length > 0 ? null : "No output."))
971 ? (turnItemOutputText(fetchedItem) ??
972 (outputImages.length > 0 || viewedImagePath ? null : "No output."))
971973 : fetchedDetail.error
972974 ? `Couldn't load output: ${fetchedDetail.error}`
973975 : row.fetchesDetail
......@@ -975,7 +977,6 @@ const ThreadWorkLogRow = memo(function ThreadWorkLogRow(
975977 ? "Output is no longer available."
976978 : "Loading output…"
977979 : null;
978 const viewedImagePath = workEntryViewedImagePath(row.workEntry);
979980 const toolPresentation = resolveWorkEntryToolPresentation(row.workEntry);
980981 const previewText = workEntryRowLabel(row.workEntry);
981982 const answerPreview = row.workEntry.questionAnswer
packages/client-runtime/src/work-log/itemDetail.test.ts+38
......@@ -52,4 +52,42 @@ describe("tool output images", () => {
5252 expect(turnItemOutputImages({ ...screenshot, output })).toEqual([]);
5353 expect(turnItemOutputText({ ...screenshot, output })).toBe("[image]");
5454 });
55
56 it("omits Claude Read's image placeholder when the file has a preview", () => {
57 const output = {
58 type: "image",
59 file: { filePath: "/tmp/blk.png", type: "image/png", sizeBytes: 1024 },
60 };
61 const item = { ...screenshot, toolName: "Read", viewedImagePath: "/tmp/blk.png", output };
62
63 expect(turnItemOutputImages(item)).toEqual([]);
64 expect(turnItemOutputText(item)).toBeNull();
65 expect(turnItemOutputText({ ...item, viewedImagePath: undefined })).toBe("[image]");
66 });
67
68 it("keeps accompanying text and literal image markers when the file has a preview", () => {
69 const output = {
70 content: [
71 { type: "text", text: "Read /tmp/blk.png. Literal marker: [image]" },
72 screenshot.output.content[1],
73 ],
74 };
75 expect(turnItemOutputText({ ...screenshot, viewedImagePath: "/tmp/blk.png", output })).toBe(
76 "Read /tmp/blk.png. Literal marker: [image]",
77 );
78 });
79
80 it("keeps placeholders for unserved images in a list despite a file preview", () => {
81 const blocks = [
82 { type: "text", text: "Read /tmp/blk.png." },
83 { type: "image", file: { type: "image/png", sizeBytes: 1024 } },
84 screenshot.output.content[1],
85 { type: "image", mimeType: "image/svg+xml" },
86 ];
87 for (const output of [blocks, { content: blocks }, { content: blocks, isError: false }]) {
88 const item = { ...screenshot, viewedImagePath: "/tmp/blk.png", output };
89 expect(turnItemOutputText(item)).toBe("Read /tmp/blk.png.\n[image]\n[image]");
90 expect(turnItemOutputImages(item)).toHaveLength(1);
91 }
92 });
5593});
packages/client-runtime/src/work-log/itemDetail.ts+6-2
......@@ -65,8 +65,10 @@ function prettyJsonText(text: string): string {
6565}
6666
6767/** Formats a tool input or output for display: text blocks as text, the rest as JSON. */
68function formatToolValue(value: unknown): string | null {
68function formatToolValue(value: unknown, hasImagePreview = false): string | null {
6969 if (value === undefined || value === null) return null;
70 // Claude Read returns one structured image; lists may contain other images.
71 if (hasImagePreview && isRecord(value) && value.type === "image") return null;
7072 const text = textFromBlocks(value, 0);
7173 if (text !== null) return text.trim() ? prettyJsonText(text) : null;
7274 let json: string | undefined;
......@@ -171,7 +173,9 @@ export function turnItemOutputText(item: OrchestrationV2TurnItem): string | null
171173 case "command_execution":
172174 return item.output?.trim() ? commandOutputText(item.output) || null : null;
173175 case "dynamic_tool":
174 return item.outputOmitted === true ? null : formatToolValue(item.output);
176 return item.outputOmitted === true
177 ? null
178 : formatToolValue(item.output, item.viewedImagePath !== undefined);
175179 case "file_search":
176180 return item.results?.length
177181 ? item.results