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}…`;