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 = ( + <> +