diff --git a/src/components/daily/__tests__/today-hero.test.tsx b/src/components/daily/__tests__/today-hero.test.tsx index 6285070da..648a838cd 100644 --- a/src/components/daily/__tests__/today-hero.test.tsx +++ b/src/components/daily/__tests__/today-hero.test.tsx @@ -185,6 +185,37 @@ describe("", () => { expect(html).toContain('data-slot="today-hero-lead"'); }); + it("falls back to the stored lines for a digest cached before the lead existed", () => { + // The service worker's offline cache can hand back a digest written by + // the previous version, which carries no `lead` field at all. + const cached = digest({ reactionLine: "The new reading fits your week." }); + delete (cached as Partial).lead; + const html = render(); + expect(visibleText(html)).toContain("The new reading fits your week."); + + const briefingOnly = digest(); + delete (briefingOnly as Partial).lead; + expect(visibleText(render())).toContain( + "Your week is trending steady.", + ); + }); + + it("does not resurrect a line the server left out of the lead", () => { + // A current digest with `lead: null` decided there is nothing to say; + // its reaction line (e.g. one that only repeated the score) stays out. + const html = render( + , + ); + expect(visibleText(html)).not.toContain("Score 82 today."); + }); + it("does not repeat the top signal under a lead made from it", () => { // A briefing whose only sentence repeated the score leads with the top // signal's headline (resolved on the server); the muted signal line under diff --git a/src/components/daily/today-hero.tsx b/src/components/daily/today-hero.tsx index a1544f88e..bdd649bb3 100644 --- a/src/components/daily/today-hero.tsx +++ b/src/components/daily/today-hero.tsx @@ -141,8 +141,13 @@ export function TodayHero({ // a briefing sentence with content while AI text is available, otherwise a // deterministic sentence about the day's strongest signal. Greetings and a // sentence that only repeats the ring's number never reach it, so the hero - // renders it verbatim and decides nothing about it. - const lead = digest.lead?.text ?? null; + // renders it verbatim and decides nothing about it. A digest the service + // worker cached before the field existed has no `lead` at all; it falls + // back to the lines that version led with. + const lead = + digest.lead === undefined + ? (digest.reactionLine ?? digest.briefingLead ?? null) + : (digest.lead?.text ?? null); const topSignal = digest.topSignal; // The briefing's top signal rides under an AI lead as its supporting line, // and only with what the lead does not already say: a headline the lead