From d67b899138e4129bdd136b01595d3c38f2225f13 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andr=C3=A9=20Bombeck?= Date: Wed, 29 Jul 2026 14:27:45 +0200 Subject: [PATCH] fix(01-16): remove redundant score and summary presentation - keep useful Today prose without repeating the ring score - remove stored-summary regeneration and Health Score notice/method/list copy --- src/components/daily/today-hero.tsx | 45 +++++++++++-- .../documents/document-summary-block.tsx | 29 +-------- src/components/insights/health-score-card.tsx | 64 +------------------ 3 files changed, 42 insertions(+), 96 deletions(-) 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({
{/* Hero numeric face: the read leads large in the foreground token, calm and legible — the day's read, not a slogan. */} -
- -
+ {lead ? ( +
+ +
+ ) : null} {/* Top signal — present-tense headline + optional delta, one muted step down so it supports the lead without competing. */} {topSignal ? ( diff --git a/src/components/documents/document-summary-block.tsx b/src/components/documents/document-summary-block.tsx index 6b0bdc16f..bee251687 100644 --- a/src/components/documents/document-summary-block.tsx +++ b/src/components/documents/document-summary-block.tsx @@ -27,7 +27,7 @@ * provider call. With no AI provider configured the block renders the state * without an action rather than an button that would 422. */ -import { FileText, RefreshCw } from "lucide-react"; +import { FileText } from "lucide-react"; import { Button } from "@/components/ui/button"; import { useTranslations } from "@/lib/i18n/context"; @@ -63,9 +63,8 @@ export function DocumentSummaryBlock({ }) { const { t } = useTranslations(); - // A stored summary wins over every state: it is shown from storage. The - // adjacent action is explicit, so opening the sheet never spends a provider - // call; a successful replacement is persisted by the summary route. + // A stored summary wins over every state and is shown read-only from storage. + // Generation remains available only for absent terminal states below. if (summary) { return (
@@ -76,28 +75,6 @@ export function DocumentSummaryBlock({

{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 (
- {algorithmNotice && !noticeDismissed ? ( -
-

- {t("insights.healthScore.algorithmChanged")} -

- -
- ) : null} - {composite.status === "ok" ? ( <> -

- {t("insights.healthScore.composition", { - pillars: composite.value.composition - .map((id) => t(PILLAR_LABEL_KEY[id])) - .join(", "), - })} -

{t("insights.healthScore.bandSetter", { pillar: composite.value.bandSetter