From 4c893878bb1f660279958d2068da8bd7f2f3b257 Mon Sep 17 00:00:00 2001 From: Sam Kelly Date: Sat, 31 Oct 2026 10:40:12 +1300 Subject: [PATCH] fix(shared): truncate keeps emoji whole slice() counts UTF-16 units, so an odd maxLength split a surrogate pair and left a broken character in titles and work-log labels. Cut by code point. --- packages/shared/src/String.test.ts | 18 ++++++++++++++++++ packages/shared/src/String.ts | 5 +++-- 2 files changed, 21 insertions(+), 2 deletions(-) create mode 100644 packages/shared/src/String.test.ts diff --git a/packages/shared/src/String.test.ts b/packages/shared/src/String.test.ts new file mode 100644 index 0000000000000..ae06e4153e4fb --- /dev/null +++ b/packages/shared/src/String.test.ts @@ -0,0 +1,18 @@ +import { describe, expect, it } from "vite-plus/test"; + +import { truncate } from "./String.ts"; + +describe("truncate", () => { + it("leaves short text alone", () => { + expect(truncate("hello", 50)).toBe("hello"); + }); + + it("cuts by Unicode code point so emoji stay whole", () => { + expect(truncate("👍👍👍", 5)).toBe("👍👍👍"); + expect(truncate("👍👍👍👍", 3)).toBe("👍👍👍..."); + }); + + it("trims before measuring", () => { + expect(truncate(" hello ", 5)).toBe("hello"); + }); +}); diff --git a/packages/shared/src/String.ts b/packages/shared/src/String.ts index c93d0c90cb6bb..f06282c030c47 100644 --- a/packages/shared/src/String.ts +++ b/packages/shared/src/String.ts @@ -1,8 +1,9 @@ export function truncate(text: string, maxLength = 50): string { const trimmed = text.trim(); - if (trimmed.length <= maxLength) { + const characters = Array.from(trimmed); + if (characters.length <= maxLength) { return trimmed; } - return `${trimmed.slice(0, maxLength)}...`; + return `${characters.slice(0, maxLength).join("")}...`; }