fix(dashboard): lead with the stored lines for a digest cached before the lead

The hero reads only digest.lead. A digest the service worker cached
before the update carries no such field, so the hero lost its lead line
until the next fetch. Such a digest now falls back to the reaction line
or the briefing lead it was built to show; a current digest whose lead
is null stays silent.
This commit is contained in:
Marc-André Bombeck committed 2026-10-05 08:34:27 +02:00
1 parent 94e0da7953
commit 3b283507b5
2 files changed
+38 -2

No files matched your search

@@ -185,6 +185,37 @@ describe("<TodayHero>", () => {
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<DailyDigest>).lead;
const html = render(<TodayHero digest={cached} />);
expect(visibleText(html)).toContain("The new reading fits your week.");
const briefingOnly = digest();
delete (briefingOnly as Partial<DailyDigest>).lead;
expect(visibleText(render(<TodayHero digest={briefingOnly} />))).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(
<TodayHero
digest={digest({
lead: null,
briefingLead: null,
topSignal: null,
reactionLine: "Score 82 today.",
})}
/>,
);
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
+7 -2
View File
@@ -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