From abb1c9f8a95bfe79313eb98674e88a99e29a2135 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andr=C3=A9=20Bombeck?= Date: Mon, 5 Oct 2026 13:11:29 +0200 Subject: [PATCH] fix(daily): count the ellipsis toward the lead budget Text with no space inside the budget was cut at 160 characters and then given an ellipsis, so the lead came out 161 long. The ellipsis now counts toward the budget: a space at the edge still ends a whole word, and unbroken text is cut one short. --- src/lib/daily/__tests__/today-overview.test.ts | 18 ++++++++++++++++++ src/lib/daily/today-overview.ts | 10 +++++++--- 2 files changed, 25 insertions(+), 3 deletions(-) diff --git a/src/lib/daily/__tests__/today-overview.test.ts b/src/lib/daily/__tests__/today-overview.test.ts index 4646f9813..6e25dcc1a 100644 --- a/src/lib/daily/__tests__/today-overview.test.ts +++ b/src/lib/daily/__tests__/today-overview.test.ts @@ -864,3 +864,21 @@ describe("signal line — a metric is named by whole words, not by fragments", ( expect(lineFor(lead, metric)).toEqual({ headline: null, delta: DELTA }); }); }); + +describe("lead length — the ellipsis counts toward the budget", () => { + it("stays within 160 characters for text without a single space", () => { + // A digit keeps it from reading as a greeting (too few words). + const unbroken = `${"a".repeat(400)}1.`; + const lead = firstSubstantiveSentence(unbroken, null) ?? ""; + expect(lead.endsWith("…")).toBe(true); + expect(lead.length).toBeLessThanOrEqual(160); + }); + + it("stays within 160 characters when the last space sits at the budget", () => { + const text = `${"b".repeat(159)} ${"c".repeat(39)}1.`; + const lead = firstSubstantiveSentence(text, null) ?? ""; + expect(lead.length).toBeLessThanOrEqual(160); + // The space at the edge still ends a whole word. + expect(lead).toBe(`${"b".repeat(159)}…`); + }); +}); diff --git a/src/lib/daily/today-overview.ts b/src/lib/daily/today-overview.ts index bb22c469f..726b14983 100644 --- a/src/lib/daily/today-overview.ts +++ b/src/lib/daily/today-overview.ts @@ -275,9 +275,13 @@ function fitsLead(text: string): boolean { */ function shortenAtWord(text: string): string { if (fitsLead(text)) return text; - const room = text.slice(0, MAX_LEAD_LENGTH); - const space = room.lastIndexOf(" "); - const head = (space > 0 ? room.slice(0, space) : room) + // One character of the budget goes to the ellipsis itself, so the kept + // head is at most MAX - 1 long: a space at that index still ends a whole + // word, and text with no space at all is cut hard one short of the budget. + const space = text.slice(0, MAX_LEAD_LENGTH).lastIndexOf(" "); + const head = ( + space > 0 ? text.slice(0, space) : text.slice(0, MAX_LEAD_LENGTH - 1) + ) .trimEnd() .replace(/[\s,;:–—-]+$/u, ""); return `${head}…`;