From f931f2f5c2b0df0fb1f8e6e41d82b9c00563abba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andr=C3=A9=20Bombeck?= Date: Fri, 19 Jun 2026 19:37:01 +0200 Subject: [PATCH] feat(insights): render signals of the day and remove the overview ask-coach block MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Render the new signalsOfDay list at the top of the daily-briefing card — a present-focused row (headline + concrete nudge) sharing the tone bar, metric icon, and sub-page routing of the key-finding row, with matching typography (text-sm headline, text-xs muted body) so the card reads consistently with the rest of the insight surfaces. Add the signalsTitle string across all six locales. Remove the "Ask the coach" action button and the guided-questions chip strip from the overview hero band: the Coach stays the bottom-right drawer mounted by the insights layout shell. The hero is now greeting + subtitle + baseline meta + the optional Health Score panel, and no longer reads the coach flags or the launch context. Update the coach-cascade / disable-cascade fixtures and the hero-strip slot tests to pin the affordances absent. --- messages/de.json | 1 + messages/en.json | 1 + messages/es.json | 1 + messages/fr.json | 1 + messages/it.json | 1 + messages/pl.json | 1 + src/app/insights/page.tsx | 19 +-- .../insights/__tests__/hero-strip.test.tsx | 62 +++------ src/components/insights/daily-briefing.tsx | 96 ++++++++++++- src/components/insights/hero-strip.tsx | 129 ++---------------- .../__tests__/coach-cascade.test.tsx | 34 ++--- .../__tests__/coach-user-disable.test.tsx | 19 +-- 12 files changed, 156 insertions(+), 209 deletions(-) diff --git a/messages/de.json b/messages/de.json index da00a7739..a39f15431 100644 --- a/messages/de.json +++ b/messages/de.json @@ -2003,6 +2003,7 @@ "dailyBriefing": { "title": "Tagesbriefing", "keyFindingsTitle": "Wichtige Befunde", + "signalsTitle": "Signale des Tages", "emptyTitle": "Noch kein Briefing", "emptyDescription": "Erstelle das heutige Briefing, um eine Zusammenfassung deiner Trends zu bekommen.", "emptyAction": "Briefing erstellen", diff --git a/messages/en.json b/messages/en.json index 4a9917e07..6c8e829fb 100644 --- a/messages/en.json +++ b/messages/en.json @@ -2003,6 +2003,7 @@ "dailyBriefing": { "title": "Daily Briefing", "keyFindingsTitle": "Key findings", + "signalsTitle": "Signals of the day", "emptyTitle": "No briefing yet", "emptyDescription": "Generate today's briefing to get a narrative read of your trends.", "emptyAction": "Generate briefing", diff --git a/messages/es.json b/messages/es.json index d922014dd..31455b452 100644 --- a/messages/es.json +++ b/messages/es.json @@ -2003,6 +2003,7 @@ "dailyBriefing": { "title": "Resumen del día", "keyFindingsTitle": "Hallazgos clave", + "signalsTitle": "Señales del día", "emptyTitle": "Aún no hay resumen", "emptyDescription": "Genera el resumen de hoy para tener una vista rápida de tus tendencias.", "emptyAction": "Generar resumen", diff --git a/messages/fr.json b/messages/fr.json index 4837f53e2..0a88a045f 100644 --- a/messages/fr.json +++ b/messages/fr.json @@ -2003,6 +2003,7 @@ "dailyBriefing": { "title": "Briefing du jour", "keyFindingsTitle": "Constats clés", + "signalsTitle": "Signaux du jour", "emptyTitle": "Pas encore de briefing", "emptyDescription": "Génère le briefing du jour pour obtenir un récap de tes tendances.", "emptyAction": "Générer le briefing", diff --git a/messages/it.json b/messages/it.json index a285b0228..dfd76eb32 100644 --- a/messages/it.json +++ b/messages/it.json @@ -2003,6 +2003,7 @@ "dailyBriefing": { "title": "Briefing del giorno", "keyFindingsTitle": "Riscontri principali", + "signalsTitle": "Segnali del giorno", "emptyTitle": "Ancora nessun briefing", "emptyDescription": "Genera il briefing di oggi per avere un riepilogo dei tuoi trend.", "emptyAction": "Genera briefing", diff --git a/messages/pl.json b/messages/pl.json index 8d176eccc..b6d2457e5 100644 --- a/messages/pl.json +++ b/messages/pl.json @@ -2003,6 +2003,7 @@ "dailyBriefing": { "title": "Briefing dnia", "keyFindingsTitle": "Kluczowe wnioski", + "signalsTitle": "Sygnały dnia", "emptyTitle": "Jeszcze brak briefingu", "emptyDescription": "Wygeneruj dzisiejszy briefing, aby zobaczyć podsumowanie trendów.", "emptyAction": "Wygeneruj briefing", diff --git a/src/app/insights/page.tsx b/src/app/insights/page.tsx index 2b13d97e3..36ec5ed8e 100644 --- a/src/app/insights/page.tsx +++ b/src/app/insights/page.tsx @@ -24,7 +24,6 @@ import { Skeleton } from "@/components/ui/skeleton"; import { cn } from "@/lib/utils"; import { HeroStrip } from "@/components/insights/hero-strip"; import { useInsightsAdvisorQuery } from "@/components/insights/use-insights-advisor"; -import { useCoachLaunch } from "@/lib/insights/coach-launch-context"; import { useAnalyticsQuery } from "@/lib/queries/use-analytics-query"; import { useDashboardDerived } from "@/components/insights/derived/use-dashboard-derived"; // v1.4.41 W-ORG — shared shape lives in `src/types/analytics.ts` as @@ -234,11 +233,11 @@ export default function InsightsPage() { useScrollResetOnRoute(); // v1.4.27 R3d MB4 — Coach drawer state lives in the layout-level - // `` so every routed sub-page can reach it. - // The hero strip + suggested-prompt chips call `askCoach(prefill)` - // on the same context, and the drawer itself is mounted next to - // the provider in `src/app/insights/layout.tsx`. - const coachLaunch = useCoachLaunch(); + // ``; the drawer is mounted next to the provider in + // `src/app/insights/layout.tsx`. v1.18.7 removed the hero-band coach + // entry points (the action button + suggested-prompt chips), so the + // overview page no longer reads the launch context — the drawer + its + // own launcher stay untouched. const flags = useFeatureFlags(); // v1.4.36 W1 — drop the page-level `isLoading` gate that used to @@ -471,14 +470,6 @@ export default function InsightsPage() { briefing={briefingPayload} updatedAt={heroStripUpdatedAt} userName={heroGreetingName} - onAskCoach={ - coachLaunch - ? (prefill?: string) => coachLaunch.askCoach(prefill ?? null) - : undefined - } - onPickPrompt={ - coachLaunch ? (prompt) => coachLaunch.askCoach(prompt) : undefined - } healthScore={analytics?.healthScore ?? undefined} // v1.16.8 — reserve the score card's column while the thick // analytics payload is in flight. Every overview query diff --git a/src/components/insights/__tests__/hero-strip.test.tsx b/src/components/insights/__tests__/hero-strip.test.tsx index 797e136b2..588d57901 100644 --- a/src/components/insights/__tests__/hero-strip.test.tsx +++ b/src/components/insights/__tests__/hero-strip.test.tsx @@ -6,8 +6,9 @@ import type { DailyBriefing as DailyBriefingPayload } from "@/lib/ai/schema"; /** * Insights hero strip — pins the slots so future polish can't - * silently drop the greeting / subtitle / action row / suggested- - * prompt strip. + * silently drop the greeting / subtitle / baseline meta. v1.18.7 + * removed the coach action row + suggested-prompt strip; the negative + * tests below pin their absence. */ function render(node: React.ReactNode, locale: "en" | "de" = "en") { @@ -121,26 +122,14 @@ describe("", () => { ); }); - it("renders the ask-the-coach action button as disabled with a 'Coming soon' title when no handler is supplied", () => { + // v1.18.7 — the overview "Ask the coach" action button was removed from + // the hero band. The Coach lives in the bottom-right drawer (mounted by + // the insights layout shell), not as a hero affordance. + it("does NOT render an ask-the-coach action button", () => { const html = render(); - const coachTag = html.match( - /]*data-slot="insights-hero-strip-action-coach"[^>]*>/, + expect(html).not.toContain( + 'data-slot="insights-hero-strip-action-coach"', ); - expect(coachTag).not.toBeNull(); - expect(coachTag?.[0]).toMatch(/\sdisabled(=""|\s|>)/); - expect(coachTag?.[0]).toContain('title="Coming soon"'); - }); - - it("enables the ask-the-coach button when onAskCoach is supplied (B2b)", () => { - const html = render( - {}} />, - ); - const coachTag = html.match( - /]*data-slot="insights-hero-strip-action-coach"[^>]*>/, - ); - expect(coachTag).not.toBeNull(); - expect(coachTag?.[0]).not.toMatch(/\sdisabled(=""|\s|>)/); - expect(coachTag?.[0]).not.toContain('title="Coming soon"'); }); // v1.4.25 W3 — the regenerate button moved from the hero action row @@ -152,20 +141,13 @@ describe("", () => { }); // ── Suggested prompts strip ──────────────────────────────────────── - it("mounts the SuggestedPrompts strip below the action band", () => { + // v1.18.7 — the guided-questions chip strip ("Try asking …") was removed + // from the hero band along with the coach action row. + it("does NOT mount the SuggestedPrompts strip in the hero band", () => { const html = render(); - expect(html).toMatch(/data-slot="insights-hero-strip-prompts"/); - expect(html).toMatch(/data-slot="insights-suggested-prompts"/); - expect(html).toContain("Try asking"); - expect(html).toContain("What should I tell my doctor?"); - }); - - it("forwards onPickPrompt down to the prompt strip without throwing", () => { - const html = render( - {}} />, - ); - // The chips render verbatim; SSR doesn't run click handlers. - expect(html).toMatch(/data-slot="insights-suggested-prompts-chip"/); + expect(html).not.toContain('data-slot="insights-hero-strip-prompts"'); + expect(html).not.toContain('data-slot="insights-suggested-prompts"'); + expect(html).not.toContain("What should I tell my doctor?"); }); // ── Misc ─────────────────────────────────────────────────────────── @@ -229,12 +211,11 @@ describe("", () => { expect(html).toContain(">86<"); }); - it("does not render an inline Ask-the-Coach button inside the Health Score card", () => { - // v1.4.27 F8 — the inline HSC Ask-the-Coach button retired in - // favour of the hero strip's existing action-row button. Even - // when `onAskCoach` is supplied, the HSC panel must not surface - // its own button. The action-row button at - // `insights-hero-strip-action-coach` carries the affordance. + it("does not render any Ask-the-Coach affordance in the hero band", () => { + // v1.4.27 F8 — the inline HSC Ask-the-Coach button was retired. + // v1.18.7 — the hero action-row coach button was removed too; the + // Coach is the bottom-right drawer, not a hero affordance. Neither + // the score card nor the band surfaces a coach button. const html = render( ", () => { }, delta: 5, }} - onAskCoach={() => {}} />, ); expect(html).not.toContain('data-slot="health-score-card-ask-coach"'); - expect(html).toContain('data-slot="insights-hero-strip-action-coach"'); + expect(html).not.toContain('data-slot="insights-hero-strip-action-coach"'); }); it("uses the lg row layout when healthScore is supplied (smoke test on container class)", () => { diff --git a/src/components/insights/daily-briefing.tsx b/src/components/insights/daily-briefing.tsx index ab156cc2e..f62aa1cfc 100644 --- a/src/components/insights/daily-briefing.tsx +++ b/src/components/insights/daily-briefing.tsx @@ -31,6 +31,7 @@ import { cn } from "@/lib/utils"; import type { DailyBriefing as DailyBriefingPayload, DailyBriefingKeyFinding, + DailyBriefingSignal, } from "@/lib/ai/schema"; /** @@ -243,6 +244,73 @@ function KeyFindingRow({ finding }: { finding: DailyBriefingKeyFinding }) { ); } +/** + * v1.18.7 — "Signals of the day" row. The present-focused lead of the + * briefing: a NOW-anchored headline + a concrete nudge the user can act on. + * Shares the tone bar + metric icon visual language of the key-finding row, + * and pins the same type/route mapping so a signal is tappable when its + * metric owns a sub-page. Typography matches the key-finding row (the rest of + * the insight cards): `text-sm` headline, `text-xs` muted body. + */ +function SignalRow({ signal }: { signal: DailyBriefingSignal }) { + const Icon = METRIC_ICON[signal.sourceMetric]; + const href = METRIC_HREF[signal.sourceMetric]; + const rowContent = ( + <> +