fix: preserve markdown formatting around media attachments (#116786)

Co-authored-by: Peter Steinberger <steipete@macos.shared>
This commit is contained in:
Peter Steinberger
2026-07-31 04:20:39 -07:00
committed by GitHub
parent 82231b425b
commit b4e10e8a90
8 changed files with 300 additions and 32 deletions
@@ -432,6 +432,86 @@ describe("message-normalizer", () => {
]);
});
it("preserves paragraph breaks and code indentation before an assistant attachment", () => {
const text = [
"Here is the code.",
"",
"```python",
"def run():",
" if ready:",
" return True",
"```",
"",
"The attachment is ready.",
].join("\n");
expect(
normalizeMessage({
role: "assistant",
content: `${text}\nMEDIA:https://example.com/image.png`,
}).content,
).toEqual([
{ type: "text", text },
{
type: "attachment",
attachment: {
url: "https://example.com/image.png",
kind: "image",
label: "image.png",
mimeType: "image/png",
},
},
]);
});
it.each(["", " ", "\t"])(
"preserves a %j paragraph separator around an assistant attachment",
(whitespace) => {
expect(
normalizeMessage({
role: "assistant",
content: `First paragraph\n${whitespace}\nMEDIA:https://example.com/image.png\n${whitespace}\nSecond paragraph`,
}).content,
).toEqual([
{ type: "text", text: "First paragraph\n" },
{
type: "attachment",
attachment: {
url: "https://example.com/image.png",
kind: "image",
label: "image.png",
mimeType: "image/png",
},
},
{ type: "text", text: "Second paragraph" },
]);
},
);
it("preserves canonical code fences after removing reply and audio directives", () => {
const code = ["```python", "value = 'a b'", "``` not a close", "other = 'c d'", "```"].join(
"\n",
);
expect(
normalizeMessage({
role: "assistant",
content: `[[reply_to_current]]\n[[audio_as_voice]]\n${code}\nMEDIA:https://example.com/image.png`,
}).content,
).toEqual([
{ type: "text", text: code },
{
type: "attachment",
attachment: {
url: "https://example.com/image.png",
kind: "image",
label: "image.png",
mimeType: "image/png",
},
},
]);
});
it("marks media-only audio attachments as voice notes when audio_as_voice is present", () => {
const result = normalizeMessage({
role: "assistant",
+2 -1
View File
@@ -412,7 +412,8 @@ function expandTextContent(text: string): {
replyTarget = { kind: "current" };
}
if (directives.text) {
parts.push({ type: "text", text: directives.text });
const normalizedText = directives.text + (segment.text.endsWith("\n") ? "\n" : "");
parts.push({ type: "text", text: normalizedText });
}
}
for (const preview of extracted.previews) {
@@ -622,6 +622,21 @@ afterEach(() => {
});
describe("grouped chat rendering", () => {
it("preserves paragraph breaks around assistant attachments in rendered markdown", () => {
const container = document.createElement("div");
renderAssistantMessage(container, {
role: "assistant",
content: "First paragraph\n \nMEDIA:https://example.com/image.png\n\t\nSecond paragraph",
timestamp: 1000,
});
expect(markdownRenderMock).toHaveBeenCalledWith(
"First paragraph\n\nSecond paragraph",
expect.any(Object),
);
});
it("renders a compact count for collapsed duplicate messages", () => {
const container = document.createElement("div");
renderAssistantMessageEntries(container, [