From 5688a47f343c219b4d25c69ecccd74a0ab3d931f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andr=C3=A9=20Bombeck?= Date: Sun, 17 May 2026 14:21:48 +0200 Subject: [PATCH] fix(insights): gate HeroStrip coach button on the global Coach flag MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The hero band carries three Coach affordances — the action-row 'Ask the coach' button, the SuggestedPrompts chip strip, and (via prop drilling) the HealthScoreCard's onAskCoach handler. All three open the same Coach drawer that the layout already gates on the operator's global Coach flag. Until now they painted unconditionally; users on a Coach-disabled deployment would click the chips or button and the drawer mount would refuse to render, leaving the controls inert. Read flags.coach in the hero and short-circuit each Coach surface when the flag is off. The action row + chip strip are conditionally rendered; the HSC's onAskCoach prop drills through as undefined so a future re-addition of an inline HSC Coach button stays gated too. --- src/components/insights/hero-strip.tsx | 110 ++++++++++++++++--------- 1 file changed, 71 insertions(+), 39 deletions(-) diff --git a/src/components/insights/hero-strip.tsx b/src/components/insights/hero-strip.tsx index 7d2f936f6..6efb3d65a 100644 --- a/src/components/insights/hero-strip.tsx +++ b/src/components/insights/hero-strip.tsx @@ -4,6 +4,7 @@ import { Sparkles } from "lucide-react"; import { Button } from "@/components/ui/button"; import { useTranslations } from "@/lib/i18n/context"; import { formatRelativeTime } from "@/lib/i18n/relative-time"; +import { useFeatureFlags } from "@/hooks/use-feature-flags"; import { cn } from "@/lib/utils"; import { SuggestedPrompts } from "./suggested-prompts"; import { @@ -118,6 +119,14 @@ export function HeroStrip({ healthScore, }: 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(); + const coachEnabled = flags.coach; const greetingKey = resolveGreetingKey(now ?? new Date()); const greetingBase = t(greetingKey); const greeting = userName ? `${greetingBase}, ${userName}` : greetingBase; @@ -207,45 +216,61 @@ export function HeroStrip({ -
- {/* 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. */} - - {/* - * v1.4.25 W3 — the regenerate button moved to the new - * `` (icon-only RefreshCw, sticky next - * to the pill nav) so the user can re-run the analysis - * without scrolling back to the hero band. - */} -
+ {/* + * 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. + */} + {coachEnabled && ( +
+ {/* 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. */} + + {/* + * v1.4.25 W3 — the regenerate button moved to the new + * `` (icon-only RefreshCw, sticky next + * to the pill nav) so the user can re-run the analysis + * without scrolling back to the hero band. + */} +
+ )} -
- {/* - * 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. - */} - undefined)} /> -
+ {coachEnabled && ( +
+ {/* + * 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. + */} + undefined)} /> +
+ )} {healthScore && ( @@ -255,7 +280,14 @@ export function HeroStrip({ components={healthScore.components} delta={healthScore.delta} onAskCoach={ - onAskCoach ? (prefill: string) => onAskCoach(prefill) : undefined + // v1.4.37 W5 — short-circuit the prop drilling even + // though `HealthScoreCard` retired its inline button in + // v1.4.27. A future re-addition can't accidentally + // surface a Coach affordance from this card while the + // operator has the flag off. + coachEnabled && onAskCoach + ? (prefill: string) => onAskCoach(prefill) + : undefined } /> )}