diff --git a/apps/web/src/chat/threads-api.test.ts b/apps/web/src/chat/threads-api.test.ts index 717827515..611098250 100644 --- a/apps/web/src/chat/threads-api.test.ts +++ b/apps/web/src/chat/threads-api.test.ts @@ -1,5 +1,8 @@ import { afterEach, describe, expect, spyOn, test } from "bun:test"; -import { ChatApiError, sendToWorkbench } from "./threads-api"; +import { buildMailFrame } from "@corbits/mailbox"; + +import { ChatApiError, frameBody, sendToWorkbench } from "./threads-api"; +import { appendRoster, stripRoster } from "./workbench-roster"; const fetchSpy = spyOn(globalThis, "fetch"); afterEach(() => { @@ -42,3 +45,25 @@ describe("sendToWorkbench failures", () => { }); } }); + +describe("frameBody", () => { + test("a roster-bearing frame built by the mailbox hub reads back without the roster", () => { + const body = appendRoster("Hello there", [ + { name: "Ada", address: "ada@example.com", kind: "person" }, + { name: "Echo", address: "echo@example.com", kind: "agent" }, + ]); + const frame = buildMailFrame({ + from: "ada@example.com", + to: "echo@example.com", + subject: "Hi", + body, + messageId: "", + }); + const raw = btoa(String.fromCharCode(...frame)); + + const text = frameBody(raw); + + expect(text).not.toContain("\r"); + expect(stripRoster(text)).toBe("Hello there"); + }); +}); diff --git a/apps/web/src/chat/threads-api.ts b/apps/web/src/chat/threads-api.ts index 8dd06dc35..31d56c5f0 100644 --- a/apps/web/src/chat/threads-api.ts +++ b/apps/web/src/chat/threads-api.ts @@ -223,7 +223,7 @@ export function frameBody(raw: string): string { const part = frameParts(raw, "chat_frame_body").find( (leaf) => leaf.filename === undefined && leaf.contentType === "text/plain", ); - return part === undefined ? "" : utf8.decode(part.content).trim(); + return part === undefined ? "" : utf8.decode(part.content).replaceAll("\r\n", "\n").trim(); } /** Every named MIME leaf of a frame, decoded. A part counts as an