Skip to content

Commit c53b786

Browse files
committed
fix: keep the word boundary for space-separated previews
The kept-ratio fallback was added for CJK summaries whose only Latin space sits near the start, but it applied to every script. A space-separated summary ending in a long token — a URL, a compound word — lost its word boundary and was cut mid-token instead. Gate the ratio on the discarded tail actually being non-spacing script, reusing the character class the catalog search tokenizer already relies on in convex/lib/searchText.ts.
1 parent 0b51dc3 commit c53b786

2 files changed

Lines changed: 21 additions & 1 deletion

File tree

src/lib/truncateText.test.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,20 @@ describe("truncateText", () => {
3131
);
3232
});
3333

34+
it("keeps the word boundary when a long Latin word ends the slice", () => {
35+
// The only space sits before 60% of the slice, but the discarded tail is space-separated
36+
// text, so the preview must still end on a word rather than mid-word.
37+
expect(truncateText("Read this hyperextendedword", 20)).toBe("Read this…");
38+
expect(truncateText("Ship internationalization", 22)).toBe("Ship…");
39+
});
40+
41+
it("keeps the Latin word boundary when only a short CJK tail is discarded", () => {
42+
// Here the boundary already keeps most of the slice, so backtracking loses nothing but a
43+
// stray CJK character that reads as noise on its own.
44+
expect(truncateText("Deploy helper for 中文文档管理", 20)).toBe("Deploy helper for…");
45+
expect(truncateText("Release notes generator 日本語対応", 28)).toBe("Release notes generator…");
46+
});
47+
3448
it("keeps the preview budget for Chinese summaries carrying an early Latin space", () => {
3549
// The single space after "5GC" is the only space in 104 characters, so backtracking
3650
// to it would leave a three-character preview.

src/lib/truncateText.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,19 @@ export const PUBLIC_CATALOG_NAME_PREVIEW_LENGTH = 70;
44
// single Latin space near the start of a summary. Backtracking to that space would drop
55
// nearly the whole preview, so only honour a word boundary that keeps most of the slice.
66
const WORD_BOUNDARY_MIN_KEPT_RATIO = 0.6;
7+
// Same character class as the catalog search tokenizer in convex/lib/searchText.ts.
8+
const CJK_RE = /[\u4e00-\u9fff\u3400-\u4dbf\u3041-\u3096\u30a1-\u30fa\uac00-\ud7af]/;
79

810
export function truncateText(value: string, maxLength: number) {
911
const normalized = value.trim().replace(/\s+/g, " ");
1012
if (normalized.length <= maxLength) return normalized;
1113
const truncated = normalized.slice(0, Math.max(0, maxLength - 1)).trimEnd();
1214
const wordBoundary = truncated.lastIndexOf(" ");
15+
// The ratio only guards non-spacing scripts. Space-separated text keeps its word boundary
16+
// however long the trailing word is.
17+
const discardsCJK = CJK_RE.test(truncated.slice(wordBoundary + 1));
1318
const keepsMostOfSlice = wordBoundary >= truncated.length * WORD_BOUNDARY_MIN_KEPT_RATIO;
14-
const text = wordBoundary > 0 && keepsMostOfSlice ? truncated.slice(0, wordBoundary) : truncated;
19+
const honoursBoundary = wordBoundary > 0 && (keepsMostOfSlice || !discardsCJK);
20+
const text = honoursBoundary ? truncated.slice(0, wordBoundary) : truncated;
1521
return `${text}…`;
1622
}

0 commit comments

Comments
 (0)