mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-11 22:02:16 +00:00
feat(insights): plumb stripChartTokens through every AI-prose surface
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.
This commit is contained in:
1 parent
6d6f4c4efb
commit
a19768c5d4
6 files changed
+37
-14
No files matched your search
@@ -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")
|
||||
: ""}
|
||||
</div>
|
||||
{safeError && (
|
||||
<p className="text-dracula-orange/90 text-xs">{safeError}</p>
|
||||
|
||||
@@ -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 }) {
|
||||
/>
|
||||
<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">{finding.headline}</p>
|
||||
<p className="text-sm leading-snug font-medium">
|
||||
{stripChartTokens(finding.headline)}
|
||||
</p>
|
||||
<DeltaBadge delta={finding.delta} tone={finding.tone} />
|
||||
</div>
|
||||
<p className="text-muted-foreground text-xs leading-snug">
|
||||
{finding.detail}
|
||||
{stripChartTokens(finding.detail)}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -228,7 +231,7 @@ export function DailyBriefing({
|
||||
data-slot="daily-briefing-paragraph"
|
||||
className="text-foreground text-sm leading-relaxed"
|
||||
>
|
||||
{briefing.paragraph}
|
||||
{stripChartTokens(briefing.paragraph)}
|
||||
</p>
|
||||
{briefing.keyFindings.length > 0 && (
|
||||
<div className="space-y-2">
|
||||
|
||||
@@ -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}`}
|
||||
>
|
||||
<p className="text-sm leading-snug">{text}</p>
|
||||
<p className="text-sm leading-snug">
|
||||
{stripChartTokens(text)}
|
||||
</p>
|
||||
{typeof confidence === "number" && (
|
||||
<span className="shrink-0">
|
||||
<ConfidenceMeter value={confidence} variant="ring" />
|
||||
|
||||
@@ -208,15 +208,15 @@ function RationaleCard({
|
||||
<div className="space-y-1.5">
|
||||
<RationaleRow
|
||||
label={t("insights.recommendation.rationaleWindow")}
|
||||
value={rationale.dataWindow}
|
||||
value={stripChartTokens(rationale.dataWindow)}
|
||||
/>
|
||||
<RationaleRow
|
||||
label={t("insights.recommendation.rationaleComparedTo")}
|
||||
value={rationale.comparedTo}
|
||||
value={stripChartTokens(rationale.comparedTo)}
|
||||
/>
|
||||
<RationaleRow
|
||||
label={t("insights.recommendation.rationaleDeviation")}
|
||||
value={rationale.deviation}
|
||||
value={stripChartTokens(rationale.deviation)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -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"
|
||||
/>
|
||||
<div className="min-w-0 flex-1 space-y-1">
|
||||
<p className="text-foreground text-xs leading-snug">{annotation}</p>
|
||||
<p className="text-foreground text-xs leading-snug">
|
||||
{stripChartTokens(annotation)}
|
||||
</p>
|
||||
{confidence ? (
|
||||
<Badge
|
||||
data-slot="trend-annotation-confidence"
|
||||
|
||||
@@ -16,6 +16,7 @@ import {
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { useTranslations, useFormatters } from "@/lib/i18n/context";
|
||||
import { useInsightsAdvisorQuery } from "@/components/insights/use-insights-advisor";
|
||||
import { stripChartTokens } from "@/lib/insights/chart-tokens";
|
||||
import { weekISOToRange } from "@/lib/insights/week-iso";
|
||||
import type { WeeklyReport } from "@/lib/ai/schema";
|
||||
import { useAuth } from "@/hooks/use-auth";
|
||||
@@ -189,7 +190,9 @@ export function WeeklyReportPresentation({
|
||||
title={t("insights.report.sectionSummary")}
|
||||
accent="text-dracula-purple"
|
||||
>
|
||||
<p className="text-sm leading-relaxed">{report.summary}</p>
|
||||
<p className="text-sm leading-relaxed">
|
||||
{stripChartTokens(report.summary)}
|
||||
</p>
|
||||
</Section>
|
||||
|
||||
{/* Going well */}
|
||||
@@ -200,7 +203,7 @@ export function WeeklyReportPresentation({
|
||||
accent="text-dracula-green"
|
||||
icon={<CheckCircle2 className="h-3.5 w-3.5" aria-hidden="true" />}
|
||||
>
|
||||
<BulletList items={report.goingWell} />
|
||||
<BulletList items={report.goingWell.map(stripChartTokens)} />
|
||||
</Section>
|
||||
)}
|
||||
|
||||
@@ -212,7 +215,7 @@ export function WeeklyReportPresentation({
|
||||
accent="text-dracula-orange"
|
||||
icon={<Eye className="h-3.5 w-3.5" aria-hidden="true" />}
|
||||
>
|
||||
<BulletList items={report.worthWatching} />
|
||||
<BulletList items={report.worthWatching.map(stripChartTokens)} />
|
||||
</Section>
|
||||
)}
|
||||
|
||||
@@ -224,7 +227,7 @@ export function WeeklyReportPresentation({
|
||||
accent="text-dracula-cyan"
|
||||
icon={<Lightbulb className="h-3.5 w-3.5" aria-hidden="true" />}
|
||||
>
|
||||
<BulletList items={report.tips} />
|
||||
<BulletList items={report.tips.map(stripChartTokens)} />
|
||||
</Section>
|
||||
)}
|
||||
|
||||
@@ -236,7 +239,7 @@ export function WeeklyReportPresentation({
|
||||
accent="text-muted-foreground"
|
||||
>
|
||||
<p className="text-muted-foreground text-sm leading-relaxed">
|
||||
{report.dataQualityNotes}
|
||||
{stripChartTokens(report.dataQualityNotes)}
|
||||
</p>
|
||||
</Section>
|
||||
)}
|
||||
|
||||
Reference in new issue
Block a user