From 1ae11be3383e004623cf076503468cdca26aa45f Mon Sep 17 00:00:00 2001 From: Sawyer Cutler Date: Sun, 4 Oct 2026 15:37:23 -0700 Subject: [PATCH 1/2] Add tests for frameBody roster stripping on CRLF frames --- apps/web/src/chat/threads-api.test.ts | 27 ++++++++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) 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"); + }); +}); From 5cb9f68f239c9261de1ddf6581c3f27e4fbd6664 Mon Sep 17 00:00:00 2001 From: Sawyer Cutler Date: Sun, 4 Oct 2026 15:39:08 -0700 Subject: [PATCH 2/2] web chat: normalize CRLF in frameBody so the roster strips from user bubbles (CL-9909) --- apps/web/src/chat/threads-api.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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