From a19768c5d42a65ae1eb9240471de1e22a31af24f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andr=C3=A9=20Bombeck?= Date: Thu, 14 May 2026 12:47:40 +0200 Subject: [PATCH] feat(insights): plumb stripChartTokens through every AI-prose surface MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The v1.4.22 stripper only ran on the InsightAdvisorCard summary, findings, primaryRecommendation, and the RecommendationCard top-line rec text. Every other AI-prose surface bypassed it entirely, so the v1.4.25 W5b regex extension (capitalised Metric + orphan enum patterns) silently could not help them. Wire stripChartTokens() through every render path that pipes AI output to the user: - DailyBriefing — paragraph, finding.headline, finding.detail - WeeklyReport — summary, goingWell[], worthWatching[], tips[], dataQualityNotes - TrendAnnotation — annotation sentence - RecommendationCard — rationale rows (dataWindow, comparedTo, deviation) inside the expanded RationaleCard - InsightsCardPreview — dashboard mini-card rec text - Coach message-thread — assistant bubble content (streaming + persisted). User bubbles stay untouched on purpose: their content is user-typed, not AI-emitted. User bubbles, evidence-block key/value rows (constrained to the sentinel grammar by the Coach prompt), and the deterministic server-generated correlation interpretations stay raw — the leak surface is AI prose, not those structured fields. --- .../insights/coach-panel/message-thread.tsx | 13 ++++++++++++- src/components/insights/daily-briefing.tsx | 9 ++++++--- src/components/insights/insights-card.tsx | 5 ++++- src/components/insights/recommendation-card.tsx | 6 +++--- src/components/insights/trend-annotation.tsx | 5 ++++- src/components/insights/weekly-report-view.tsx | 13 ++++++++----- 6 files changed, 37 insertions(+), 14 deletions(-) diff --git a/src/components/insights/coach-panel/message-thread.tsx b/src/components/insights/coach-panel/message-thread.tsx index 216c1d3c4..5752528b9 100644 --- a/src/components/insights/coach-panel/message-thread.tsx +++ b/src/components/insights/coach-panel/message-thread.tsx @@ -13,6 +13,7 @@ import { import { cn } from "@/lib/utils"; import { useTranslations } from "@/lib/i18n/context"; +import { stripChartTokens } from "@/lib/insights/chart-tokens"; import { useAuth } from "@/hooks/use-auth"; import { useCoachPrefs } from "@/hooks/use-coach-prefs"; @@ -330,7 +331,17 @@ function ChatBubble({ inProgress && "animate-pulse motion-reduce:animate-none", )} > - {content || (inProgress ? t("insights.coach.thinking") : "")} + {/* v1.4.25 W5b — strip stray Metric/enum leak tokens from + the assistant prose before it lands in the bubble. The + raw `content` is the streamed Coach reply (or the + persisted twin after `done` fires); both paths are AI- + authored so they share the same leak surface as the + insight prose elsewhere. */} + {content + ? stripChartTokens(content) + : inProgress + ? t("insights.coach.thinking") + : ""} {safeError && (

{safeError}

diff --git a/src/components/insights/daily-briefing.tsx b/src/components/insights/daily-briefing.tsx index 8b8872145..91e148b82 100644 --- a/src/components/insights/daily-briefing.tsx +++ b/src/components/insights/daily-briefing.tsx @@ -23,6 +23,7 @@ import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { EmptyState } from "@/components/ui/empty-state"; import { useTranslations } from "@/lib/i18n/context"; import { formatRelativeTime } from "@/lib/i18n/relative-time"; +import { stripChartTokens } from "@/lib/insights/chart-tokens"; import { cn } from "@/lib/utils"; import type { DailyBriefing as DailyBriefingPayload, @@ -143,11 +144,13 @@ function KeyFindingRow({ finding }: { finding: DailyBriefingKeyFinding }) { />
-

{finding.headline}

+

+ {stripChartTokens(finding.headline)} +

- {finding.detail} + {stripChartTokens(finding.detail)}

@@ -228,7 +231,7 @@ export function DailyBriefing({ data-slot="daily-briefing-paragraph" className="text-foreground text-sm leading-relaxed" > - {briefing.paragraph} + {stripChartTokens(briefing.paragraph)}

{briefing.keyFindings.length > 0 && (
diff --git a/src/components/insights/insights-card.tsx b/src/components/insights/insights-card.tsx index d2321f696..c481277f5 100644 --- a/src/components/insights/insights-card.tsx +++ b/src/components/insights/insights-card.tsx @@ -5,6 +5,7 @@ import { Sparkles, ArrowRight } from "lucide-react"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { Button } from "@/components/ui/button"; import { useTranslations } from "@/lib/i18n/context"; +import { stripChartTokens } from "@/lib/insights/chart-tokens"; import type { InsightResult } from "@/lib/ai/types"; import { sortRecommendationsBySeverity } from "./recommendations-grid"; import { ConfidenceMeter } from "./confidence-meter"; @@ -101,7 +102,9 @@ export function InsightsCardPreview({ key={id ?? index} className={`bg-card/40 flex items-start justify-between gap-2 rounded-md border-l-2 px-3 py-2 ${borderClass}`} > -

{text}

+

+ {stripChartTokens(text)} +

{typeof confidence === "number" && ( diff --git a/src/components/insights/recommendation-card.tsx b/src/components/insights/recommendation-card.tsx index cf3d5e63f..822835157 100644 --- a/src/components/insights/recommendation-card.tsx +++ b/src/components/insights/recommendation-card.tsx @@ -208,15 +208,15 @@ function RationaleCard({
diff --git a/src/components/insights/trend-annotation.tsx b/src/components/insights/trend-annotation.tsx index 4c14fdab9..5eeaf53b5 100644 --- a/src/components/insights/trend-annotation.tsx +++ b/src/components/insights/trend-annotation.tsx @@ -3,6 +3,7 @@ import { Sparkles } from "lucide-react"; import { Badge } from "@/components/ui/badge"; import { useTranslations } from "@/lib/i18n/context"; +import { stripChartTokens } from "@/lib/insights/chart-tokens"; import { cn } from "@/lib/utils"; import { CONFIDENCE_BADGE_CLASS } from "./confidence-badge"; @@ -73,7 +74,9 @@ export function TrendAnnotation({ aria-hidden="true" />
-

{annotation}

+

+ {stripChartTokens(annotation)} +

{confidence ? ( -

{report.summary}

+

+ {stripChartTokens(report.summary)} +

{/* Going well */} @@ -200,7 +203,7 @@ export function WeeklyReportPresentation({ accent="text-dracula-green" icon={