diff --git a/web/src/components/MessageList.test.tsx b/web/src/components/MessageList.test.tsx index fa06a5f..d2ac203 100644 --- a/web/src/components/MessageList.test.tsx +++ b/web/src/components/MessageList.test.tsx @@ -455,6 +455,45 @@ describe("MessageList task diagnostics", () => { expect(html).toContain("The latest turn could not be loaded."); }); + it("renders attachments inside a session-start framed message", () => { + const framed: DisplayMessage = { + id: "cydo-start-1", + uuid: "start-uuid-1", + type: "user", + isMeta: true, + content: [ + { type: "text", text: "SYSTEM rendered prompt body" }, + { type: "image", data: "aGVsbG8=", media_type: "image/png" }, + ], + cydoMeta: { + label: "Session start: blank", + vars: { task_description: "what is in this picture?" }, + bodyVar: "task_description", + }, + }; + + const html = renderToString( + , + ); + + // the framed view must keep the attachment visible, not just the text: + // losing it on replay made a briefly-shown image vanish from the task + expect(html).toContain("user-image"); + expect(html).toContain("data:image/png;base64,aGVsbG8="); + expect(html).toContain("what is in this picture?"); + // attachment above the prompt, matching plain user messages + expect(html.indexOf("data:image/png")).toBeLessThan( + html.indexOf("what is in this picture?"), + ); + }); + it("keeps a metadata-bearing CyDo nudge editable", () => { const nudge: DisplayMessage = { id: "cydo-nudge-1", diff --git a/web/src/components/MessageList.tsx b/web/src/components/MessageList.tsx index fd805fe..9421e33 100644 --- a/web/src/components/MessageList.tsx +++ b/web/src/components/MessageList.tsx @@ -218,6 +218,28 @@ function SystemUserMessage({ message }: { message: DisplayMessage }) { .map((b) => b.text) .join("\n"); + // Attachments survive the session-start framing (the backend keeps image + // blocks beside the rendered prompt), so they must survive its rendering + // too; the optimistic plain user message showed them, and losing them on + // replay looked like the attachment vanished. + const imageBlocks = message.content.filter( + (b): b is { type: "image"; data: string; media_type: string } => + b.type === "image" && + typeof (b as Record).data === "string", + ); + const images = imageBlocks.length > 0 && ( + + {imageBlocks.map((img, i) => ( + + ))} + + ); + const hasVars = meta.vars && Object.keys(meta.vars).length > 0; if (!hasVars) { @@ -262,6 +284,7 @@ function SystemUserMessage({ message }: { message: DisplayMessage }) { }} > {meta.label} + {images} {text} ); @@ -297,6 +320,7 @@ function SystemUserMessage({ message }: { message: DisplayMessage }) { {meta.label} + {images} {bodyValue !== undefined && ( {meta.bodyMarkdown ? (
{text}