diff --git a/src/components/daily/today-hero.tsx b/src/components/daily/today-hero.tsx index b28a536ca..b5a18d4f9 100644 --- a/src/components/daily/today-hero.tsx +++ b/src/components/daily/today-hero.tsx @@ -64,6 +64,28 @@ function formatDelta( return t("daily.today.deltaVsBaseline", { delta: signed }); } +/** + * The ring already owns the headline number. AI briefing copy can still carry + * an older lead sentence that repeats that exact score, so remove only the + * sentence containing the displayed number and retain every other useful + * sentence verbatim. If the score sentence was the whole lead, render no lead + * rather than inventing replacement prose. + */ +function withoutRepeatedScore( + text: string | null, + score: number | null, +): string | null { + if (!text || score === null) return text; + const shownScore = String(Math.round(score)); + const scorePattern = new RegExp( + `(^|[^0-9])${shownScore.replace(/[.*+?^${}()|[\\]\\\\]/g, "\\$&")}([^0-9]|$)`, + ); + const sentences = text.match(/[^.!?]+[.!?]+(?:\s+|$)|[^.!?]+$/g) ?? [text]; + const retained = sentences.filter((sentence) => !scorePattern.test(sentence)); + const result = retained.join(" ").replace(/\s+/g, " ").trim(); + return result.length > 0 ? result : null; +} + /** * The "just in" chip's clock face. * @@ -128,7 +150,14 @@ export function TodayHero({ // paragraph, so the hero stays exactly one lead line tall and nothing below // it shifts. The briefing lead is the standing read; the deterministic // `line` is the floor a keyless self-hoster still gets. - const lead = digest.reactionLine ?? digest.briefingLead ?? digest.line; + const rawLead = + digest.reactionLine ?? + digest.briefingLead ?? + // The deterministic line is a useful floor only once the ring has a real + // score. A provisional/null ring must not pair with a fabricated score + // sentence from that fallback. + (hasScore ? digest.line : null); + const lead = withoutRepeatedScore(rawLead, digest.score?.value ?? null); const topSignal = digest.topSignal; const justInTime = digest.justIn ? formatJustInTime(digest.justIn.at) : null; @@ -179,12 +208,14 @@ export function TodayHero({
{t("documents.detail.summary.title")}
- {aiEnabled ? ( - - ) : null}{summary}
{generatedAtLabel ? ( diff --git a/src/components/insights/health-score-card.tsx b/src/components/insights/health-score-card.tsx index 7a5884baa..0df398e8b 100644 --- a/src/components/insights/health-score-card.tsx +++ b/src/components/insights/health-score-card.tsx @@ -1,14 +1,7 @@ "use client"; -import { useState } from "react"; -import { X } from "lucide-react"; - import { ScoreAnatomyView } from "@/components/insights/derived/score-anatomy-view"; import { CoverageMeter } from "@/components/insights/derived/coverage-meter"; -import { Button } from "@/components/ui/button"; -import { apiPost } from "@/lib/api/api-fetch"; -import { useOptionalQueryClient } from "@/hooks/_internal/use-query-client-safe"; -import { queryKeys } from "@/lib/query-keys"; import { useAuth } from "@/hooks/use-auth"; import { useUnitDisplay } from "@/hooks/use-unit-display"; import { convertGlucose, resolveGlucoseUnit } from "@/lib/glucose"; @@ -102,10 +95,6 @@ export function HealthScoreCard({ const { t } = useTranslations(); const { user } = useAuth(); const glucoseUnit = resolveGlucoseUnit(user?.glucoseUnit); - const queryClient = useOptionalQueryClient(); - const [noticeDismissed, setNoticeDismissed] = useState( - report.algorithmNotice?.dismissed ?? false, - ); function reasonText(reason: string): string { const key = REASON_KEY[reason] ?? "unavailable"; @@ -234,55 +223,11 @@ export function HealthScoreCard({ ? 1 / eligibleCount : 0, })); - const algorithmNotice = report.algorithmNotice; - - async function dismissAlgorithmNotice() { - if (!algorithmNotice) return; - setNoticeDismissed(true); - try { - await apiPost("/api/daily/digest/dismiss", { - itemKey: algorithmNotice.itemKey, - }); - queryClient?.setQueriesData( - { queryKey: queryKeys.analytics() }, - (payload) => - markAlgorithmNoticeDismissed(payload, algorithmNotice.itemKey), - ); - await queryClient?.invalidateQueries({ - queryKey: queryKeys.analytics(), - refetchType: "active", - }); - } catch { - setNoticeDismissed(false); - } - } - return (- {t("insights.healthScore.algorithmChanged")} -
- -- {t("insights.healthScore.composition", { - pillars: composite.value.composition - .map((id) => t(PILLAR_LABEL_KEY[id])) - .join(", "), - })} -
{t("insights.healthScore.bandSetter", { pillar: composite.value.bandSetter