feat(insights): render signals of the day and remove the overview ask-coach block

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.
This commit is contained in:
Marc-André Bombeck committed 2026-06-19 19:37:01 +02:00
1 parent 0b01bfb483
commit f931f2f5c2
12 files changed
+156 -209

No files matched your search

+1
View File
@@ -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",
+1
View File
@@ -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",
+1
View File
@@ -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",
+1
View File
@@ -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",
+1
View File
@@ -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",
+1
View File
@@ -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",
+5 -14
View File
@@ -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
// `<CoachLaunchProvider>` 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();
// `<CoachLaunchProvider>`; 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
@@ -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("<HeroStrip>", () => {
);
});
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(<HeroStrip briefing={null} now={morningLocal} />);
const coachTag = html.match(
/<button[^>]*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(
<HeroStrip briefing={null} now={morningLocal} onAskCoach={() => {}} />,
);
const coachTag = html.match(
/<button[^>]*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("<HeroStrip>", () => {
});
// ── 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(<HeroStrip briefing={null} now={morningLocal} />);
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(
<HeroStrip briefing={null} now={morningLocal} onPickPrompt={() => {}} />,
);
// 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("<HeroStrip>", () => {
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(
<HeroStrip
briefing={null}
@@ -250,11 +231,10 @@ describe("<HeroStrip>", () => {
},
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)", () => {
+94 -2
View File
@@ -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 = (
<>
<span
aria-hidden="true"
className={cn(
"absolute top-3 bottom-3 left-0 w-[3px] rounded-r",
TONE_BAR_CLASSNAME[signal.tone],
)}
/>
<Icon
className={cn(
"mt-0.5 h-4 w-4 shrink-0",
TONE_TEXT_CLASSNAME[signal.tone],
)}
aria-hidden="true"
/>
<div className="min-w-0 flex-1 space-y-1">
<div className="flex items-start justify-between gap-3">
<p className="text-sm leading-snug font-medium">
{stripChartTokens(signal.headline)}
</p>
<DeltaBadge delta={signal.delta} tone={signal.tone} />
</div>
<p className="text-muted-foreground text-xs leading-snug">
{stripChartTokens(signal.nudge)}
</p>
</div>
</>
);
if (href) {
return (
<Link
href={href}
data-slot="daily-briefing-signal"
data-metric={signal.sourceMetric}
className={cn(
"border-border/60 bg-card/40 relative flex items-start gap-3 rounded-md border p-3",
"hover:bg-accent/40 transition-colors",
"focus-visible:ring-ring/50 focus-visible:ring-2 focus-visible:outline-none",
)}
>
{rowContent}
</Link>
);
}
return (
<div
data-slot="daily-briefing-signal"
data-metric={signal.sourceMetric}
className="border-border/60 bg-card/40 relative flex items-start gap-3 rounded-md border p-3"
>
{rowContent}
</div>
);
}
function BriefingSkeleton() {
return (
<div
@@ -317,8 +385,32 @@ export function DailyBriefing({
the same `briefing.paragraph` text directly above this
card, so the user used to read the same string twice
within 200 px. The card now opens straight on the
structured key-findings list, which is the part the
hero subtitle cannot surface. */}
structured signals + key-findings list, which is the part
the hero subtitle cannot surface. */}
{/* v1.18.7 — "Signals of the day": the present-focused lead.
Renders above the longer-horizon key-findings list when the
briefing carries fresh now-signals. */}
{briefing.signalsOfDay && briefing.signalsOfDay.length > 0 && (
<div className="space-y-2">
<p
data-slot="daily-briefing-signals-title"
className="text-muted-foreground text-[11px] font-semibold tracking-wide uppercase"
>
{t("insights.dailyBriefing.signalsTitle")}
</p>
<div
data-slot="daily-briefing-signals"
className="space-y-2"
>
{briefing.signalsOfDay.map((signal, index) => (
<SignalRow
key={`${signal.sourceMetric}-${index}`}
signal={signal}
/>
))}
</div>
</div>
)}
{briefing.keyFindings.length > 0 && (
<div className="space-y-2">
<p
+15 -114
View File
@@ -1,15 +1,11 @@
"use client";
import { Sparkles } from "lucide-react";
import { Button } from "@/components/ui/button";
import { Skeleton } from "@/components/ui/skeleton";
import { useTranslations } from "@/lib/i18n/context";
import { formatRelativeTime } from "@/lib/i18n/relative-time";
import { useFeatureFlags } from "@/hooks/use-feature-flags";
import { useDisableCoach } from "@/hooks/use-disable-coach";
import { useModuleEnabled } from "@/hooks/use-module-enabled";
import { cn } from "@/lib/utils";
import { SuggestedPrompts } from "./suggested-prompts";
import {
HealthScoreCard,
type HealthScoreCardProps,
@@ -26,18 +22,15 @@ import type { DailyBriefing as DailyBriefingPayload } from "@/lib/ai/schema";
* "Guten Morgen, …") + a narrative subtitle (the briefing
* paragraph when one is cached, else a fallback)
* - personal-baseline + "Generated <relative-time>" meta row
* - action row: single "Ask the coach" affordance
* (v1.4.25 W3 dropped the "Re-run analysis" button — the
* regenerate affordance moved to `<InsightsTabStrip>`. v1.4.28
* retired the weekly-report path, leaving Coach as the sole
* hero-row action.)
* - <SuggestedPrompts> chip strip below the action band
* - Dracula gradient + soft purple glow via the new `.hero-gradient`
* + `.glow-purple` utilities in `globals.css`
*
* Mobile-first: the action row wraps + the suggested-prompt chips
* wrap. Right-side Health Score panel is *not* part of B1 — that
* lands in B5; for B1 the right side just keeps its meta band.
* v1.18.7 — the overview "Ask the coach" action row + guided-questions
* chip strip were removed from the band. The Coach lives in the
* bottom-right drawer (mounted by the insights layout shell); the
* overview now leads with the present-focused daily briefing, not a
* coach prompt. The hero is greeting + subtitle + baseline meta + the
* optional right-side Health Score panel.
*
* Pure presentational — the page owns the briefing data.
*/
@@ -53,25 +46,6 @@ interface HeroStripProps {
* morning, undefined".
*/
userName?: string | null;
/**
* Click handler for the suggested prompts. The Coach drawer (B2b)
* routes a chip click into the drawer's input; the parent owns the
* drawer state. When omitted the chip click is a no-op.
*/
onPickPrompt?: (prompt: string) => void;
/**
* Click handler for the "Ask the coach" action button. When supplied,
* the button is enabled and the coming-soon tooltip drops; clicking
* opens the drawer (B2b). When omitted the button stays disabled —
* see the v1.4.20 phase B1 dispatch where the drawer was deferred.
*
* v1.4.20 phase B5 — the same handler powers the Health Score
* panel's "Ask the Coach" button, with an optional `prefill` string
* that opens the drawer with a score-aware question. The action-row
* button calls it without an argument (drawer opens blank); the HSC
* panel calls it with "Why is my health score X out of 100?".
*/
onAskCoach?: (prefill?: string) => void;
/**
* Now() override for tests so the greeting bucket is deterministic.
* Defaults to `new Date()`. Production callers omit this.
@@ -79,11 +53,8 @@ interface HeroStripProps {
now?: Date;
/**
* v1.4.20 phase B5 — Personal Health Score panel data. When supplied
* the right side of the hero band paints the score card. The
* `onAskCoach` handler is intentionally re-used from the action-row
* "Ask the coach" button — same drawer state, different prefill
* string. When the parent omits the field the right side stays empty
* (current B1 behaviour).
* the right side of the hero band paints the score card. When the
* parent omits the field the right side stays empty.
*/
healthScore?: {
score: HealthScoreCardProps["score"];
@@ -134,28 +105,11 @@ export function HeroStrip({
briefing,
updatedAt,
userName,
onPickPrompt,
onAskCoach,
now,
healthScore,
healthScorePending = false,
}: HeroStripProps) {
const { t } = useTranslations();
// v1.4.37 W5 — operator-level Coach gate. When the global Coach flag
// is off every Coach affordance must vanish from this band (the
// action-row button, the suggested-prompts chip strip, and the
// HealthScoreCard's `onAskCoach` prop). The button and chips both
// open the Coach drawer, so leaving them visible while the drawer
// mount is suppressed would surface dead controls.
const flags = useFeatureFlags();
// v1.4.47 W3 — per-user "Hide Coach" toggle joins the operator's
// master flag. Either-off resolves to `coachEnabled === false` so
// the hero action button + suggested-prompts strip + the
// `<HealthScoreCard onAskCoach />` prop all vanish together. The
// hook is SSR-safe — returns `false` when the legacy snapshot
// tests render the strip without a QueryClient.
const disableCoach = useDisableCoach();
const coachEnabled = flags.coach && !disableCoach;
// v1.18.0 R4 — mood-module state so the score card hides its Mood row
// when the account turned the module off (the server already drops the
// pillar from the number itself). SSR-safe + default-on, matching the
@@ -168,7 +122,6 @@ export function HeroStrip({
const generatedLine = updatedAt
? t("insights.heroGenerated", { time: formatRelativeTime(updatedAt, t) })
: null;
const comingSoon = t("insights.heroComingSoonTooltip");
return (
<div
@@ -256,66 +209,14 @@ export function HeroStrip({
</div>
{/*
* v1.4.37 W5 — the action row only carries the Coach button
* today (v1.4.28 retired the weekly-report affordance). When
* the operator turns the global Coach flag off the whole row
* disappears rather than collapsing to an empty flex shell.
* v1.18.7 — the overview "Ask the coach" action row + the
* guided-questions chip strip were removed from the hero band.
* The Coach is the bottom-right drawer (mounted in the insights
* layout shell); the overview leads with the present-focused
* daily briefing, not a coach prompt. The drawer itself is
* untouched — only the hero entry points are gone, so the band
* is now greeting + subtitle + baseline meta.
*/}
{coachEnabled && (
<div className="flex flex-wrap items-center justify-end gap-2">
{/* B2b wires this into the Coach drawer. The button is
enabled whenever the parent supplies an `onAskCoach`
handler; older parents that haven't adopted B2b yet
still get the disabled "Coming soon" affordance so the
hero doesn't break. v1.4.28 retired the weekly-report
button, leaving Coach as the only hero-row action. */}
<Button
type="button"
variant="outline"
onClick={onAskCoach ? () => onAskCoach() : undefined}
disabled={!onAskCoach}
title={onAskCoach ? undefined : comingSoon}
data-slot="insights-hero-strip-action-coach"
className="gap-1.5"
>
<Sparkles className="h-3.5 w-3.5" aria-hidden="true" />
<span>{t("insights.heroActionAskCoach")}</span>
</Button>
{/*
* v1.4.25 W3 — the regenerate button moved to the new
* `<InsightsTabStrip>` (icon-only RefreshCw, sticky next
* to the pill nav) so the user can re-run the analysis
* without scrolling back to the hero band.
*/}
</div>
)}
{coachEnabled && (
<div
data-slot="insights-hero-strip-prompts"
// v1.8.5 W4a — condense the "Coach fragen" band so the left
// column stops out-running the Health-Score card on the
// right. Tighter top padding (`pt-3` vs `pt-4`) plus a
// `mt-auto` push pins the chip strip to the bottom of the
// left column, so the recovered slack lands between the meta
// row and the prompts instead of stacking the column tall.
className="border-border/50 mt-auto border-t pt-3"
>
{/*
* v1.4.20 phase B2b — chip clicks open the Coach drawer
* with the localised prompt pre-filled in the composer.
* The parent owns drawer state so the chip strip stays
* presentational.
*
* v1.4.37 W5 — the strip is a Coach-only affordance
* (every chip seeds a Coach turn), so it is gated on the
* same flag as the action-row button above. Without the
* gate the chips would still paint while the drawer is
* suppressed, leaving inert controls in the band.
*/}
<SuggestedPrompts onPick={onPickPrompt ?? (() => undefined)} />
</div>
)}
</div>
{!healthScore && healthScorePending && (
@@ -13,7 +13,6 @@ import {
type AssistantFlagSet,
} from "@/hooks/use-feature-flags";
import { HeroStrip } from "@/components/insights/hero-strip";
import { SuggestedPrompts } from "@/components/insights/suggested-prompts";
import { CoachLaunchButton } from "@/components/insights/coach-launch-button";
import { LayoutCoachFab } from "@/components/insights/layout-coach-fab";
@@ -139,29 +138,11 @@ interface CoachSurface {
proofWhenOn: string;
}
// v1.18.7 — the HeroStrip "Ask the coach" action button + the in-band
// SuggestedPrompts chip strip were removed from the overview hero. The
// Coach is the bottom-right drawer; its launcher + the standalone
// SuggestedPrompts surface still carry the flag cascade below.
const COACH_SURFACES: CoachSurface[] = [
{
name: "HeroStrip action-row 'Ask the coach' button",
mount: () => (
<HeroStrip
briefing={null}
now={new Date(2026, 4, 10, 9, 0, 0)}
onAskCoach={() => undefined}
/>
),
proofWhenOn: 'data-slot="insights-hero-strip-action-coach"',
},
{
name: "HeroStrip SuggestedPrompts chip strip",
mount: () => (
<HeroStrip
briefing={null}
now={new Date(2026, 4, 10, 9, 0, 0)}
onPickPrompt={() => undefined}
/>
),
proofWhenOn: 'data-slot="insights-hero-strip-prompts"',
},
{
name: "SuggestedPrompts standalone",
mount: () => <SuggestedPrompts onPick={() => undefined} />,
@@ -290,7 +271,8 @@ describe("Coach disable cascade invariant", () => {
const KNOWN_COACH_GATE_SITES: ReadonlyArray<string> = [
// Coach-bearing surfaces mounted directly by the cascade fixture.
"src/components/insights/coach-launch-button.tsx",
"src/components/insights/hero-strip.tsx",
// v1.18.7 — hero-strip.tsx no longer reads `flags.coach`: its coach
// action button + suggested-prompt strip were removed from the band.
"src/components/insights/layout-coach-fab.tsx",
"src/components/insights/layout-coach-mount.tsx",
"src/components/insights/suggested-prompts.tsx",
@@ -404,6 +386,8 @@ describe("Coach disable cascade invariant", () => {
// forces them to revisit the fixture. The number tracks the
// surfaces the fixture mounts directly; the grep-based discovery
// test above pins every other `flags.coach` call site.
expect(COACH_SURFACES.length).toBe(6);
// v1.18.7 — dropped from 6 to 4: the two HeroStrip coach surfaces
// (action button + suggested-prompt strip) were removed from the band.
expect(COACH_SURFACES.length).toBe(4);
});
});
@@ -18,7 +18,6 @@ import { CoachLaunchButton } from "@/components/insights/coach-launch-button";
import { LayoutCoachFab } from "@/components/insights/layout-coach-fab";
import { LayoutCoachMount } from "@/components/insights/layout-coach-mount";
import { SuggestedPrompts } from "@/components/insights/suggested-prompts";
import { HeroStrip } from "@/components/insights/hero-strip";
/**
* v1.4.47 W3 — per-user "Hide Coach" opt-out invariant.
@@ -182,17 +181,9 @@ const DISABLE_COACH_SURFACES: DisableCoachSurface[] = [
mount: () => <SuggestedPrompts onPick={() => undefined} />,
proofWhenVisible: 'data-slot="insights-suggested-prompts"',
},
{
name: "HeroStrip action-row Ask the coach",
mount: () => (
<HeroStrip
briefing={null}
now={new Date(2026, 4, 10, 9, 0, 0)}
onAskCoach={() => undefined}
/>
),
proofWhenVisible: 'data-slot="insights-hero-strip-action-coach"',
},
// v1.18.7 — the HeroStrip "Ask the coach" action button was removed from
// the overview hero. The Coach is the bottom-right drawer; its launcher
// surfaces below carry the per-user disable gate.
];
describe("Coach per-user disableCoach invariant", () => {
@@ -290,7 +281,9 @@ describe("Coach per-user disableCoach invariant", () => {
// fixture above; each one calls `useDisableCoach()` and short-
// circuits to `null` when the per-user flag is on.
"src/components/insights/coach-launch-button.tsx",
"src/components/insights/hero-strip.tsx",
// v1.18.7 — hero-strip.tsx no longer gates on the Coach flag: its
// action button + suggested-prompt strip were removed, so it dropped
// the `useDisableCoach` call entirely.
"src/components/insights/layout-coach-fab.tsx",
"src/components/insights/layout-coach-mount.tsx",
"src/components/insights/suggested-prompts.tsx",