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={}
>
-
+
)}
@@ -212,7 +215,7 @@ export function WeeklyReportPresentation({
accent="text-dracula-orange"
icon={}
>
-
+
)}
@@ -224,7 +227,7 @@ export function WeeklyReportPresentation({
accent="text-dracula-cyan"
icon={}
>
-
+
)}
@@ -236,7 +239,7 @@ export function WeeklyReportPresentation({
accent="text-muted-foreground"
>
- {report.dataQualityNotes}
+ {stripChartTokens(report.dataQualityNotes)}
)}