diff --git a/apps/mobile/src/lib/projectThreadStartTurn.test.ts b/apps/mobile/src/lib/projectThreadStartTurn.test.ts index e2a41cf9d886f..6abb3409d3680 100644 --- a/apps/mobile/src/lib/projectThreadStartTurn.test.ts +++ b/apps/mobile/src/lib/projectThreadStartTurn.test.ts @@ -5,6 +5,7 @@ import { serializeComposerMessageForServer } from "./composerContext"; import { buildProjectThreadStartTurnInput, + deriveThreadTitleFromPrompt, type ProjectThreadStartTurnSpec, } from "./projectThreadStartTurn"; @@ -69,4 +70,10 @@ describe("project thread title", () => { expect(relaunched.message.text).toBe(wire.text); expect(relaunched.message.attachments).toEqual(uploadedAttachments); }); + + it("shortens a long prompt without splitting an emoji", () => { + expect(deriveThreadTitleFromPrompt(`${"a".repeat(68)}😀 and the rest of the prompt`)).toBe( + `${"a".repeat(68)}...`, + ); + }); }); diff --git a/apps/mobile/src/lib/projectThreadStartTurn.ts b/apps/mobile/src/lib/projectThreadStartTurn.ts index 1e677d232b81f..00dbadc3aff17 100644 --- a/apps/mobile/src/lib/projectThreadStartTurn.ts +++ b/apps/mobile/src/lib/projectThreadStartTurn.ts @@ -87,5 +87,12 @@ export function buildProjectThreadStartTurnInput(spec: ProjectThreadStartTurnSpe export function deriveThreadTitleFromPrompt(value: string): string { const title = threadTitleTextFromPrompt(value) || "New thread"; - return title.length <= 72 ? title : `${title.slice(0, 69).trimEnd()}...`; + if (title.length <= 72) return title; + let end = 69; + const previous = title.charCodeAt(end - 1); + const next = title.charCodeAt(end); + if (previous >= 0xd800 && previous <= 0xdbff && next >= 0xdc00 && next <= 0xdfff) { + end -= 1; + } + return `${title.slice(0, end).trimEnd()}...`; }