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
This commit is contained in:
Marc-André Bombeck committed 2026-07-29 14:27:45 +02:00
1 parent b39ddeb5b5
commit d67b899138
3 files changed
+42 -96

No files matched your search

+38 -7
View File
@@ -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({
<div className="min-w-0 flex-1 space-y-2">
{/* Hero numeric face: the read leads large in the foreground
token, calm and legible — the day's read, not a slogan. */}
<div
data-slot="today-hero-lead"
className="text-foreground text-lg leading-snug font-semibold tracking-tight sm:text-xl"
>
<ProseBlocks text={lead} strip linkify={false} />
</div>
{lead ? (
<div
data-slot="today-hero-lead"
className="text-foreground text-lg leading-snug font-semibold tracking-tight sm:text-xl"
>
<ProseBlocks text={lead} strip linkify={false} />
</div>
) : null}
{/* Top signal — present-tense headline + optional delta, one
muted step down so it supports the lead without competing. */}
{topSignal ? (
@@ -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 (
<section className="space-y-2" data-slot="document-detail-summary">
@@ -76,28 +75,6 @@ export function DocumentSummaryBlock({
<p className="text-sm leading-none font-medium">
{t("documents.detail.summary.title")}
</p>
{aiEnabled ? (
<Button
type="button"
variant="ghost"
size="sm"
data-slot="document-detail-summary-regenerate"
onClick={onGenerate}
disabled={isGenerating || actionsDisabled}
>
<RefreshCw
className={
isGenerating
? "size-4 animate-spin motion-reduce:animate-none"
: "size-4"
}
aria-hidden
/>
{isGenerating
? t("documents.detail.summary.generating")
: t("documents.detail.summary.regenerate")}
</Button>
) : null}
</div>
<p className="text-foreground text-sm">{summary}</p>
{generatedAtLabel ? (
+1 -63
View File
@@ -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 (
<div
data-slot="health-score-card"
className={cn("flex min-w-0 flex-col gap-3", className)}
>
{algorithmNotice && !noticeDismissed ? (
<div
data-slot="health-score-algorithm-notice"
className="border-border/70 bg-muted/35 flex items-start gap-3 rounded-lg border px-3 py-2.5"
>
<p className="text-muted-foreground min-w-0 flex-1 text-xs leading-relaxed">
{t("insights.healthScore.algorithmChanged")}
</p>
<Button
type="button"
variant="ghost"
size="icon"
className="-mt-1 -mr-2 h-9 w-9 shrink-0"
aria-label={t("insights.healthScore.dismissAlgorithmChanged")}
onClick={() => void dismissAlgorithmNotice()}
>
<X className="h-4 w-4" aria-hidden />
</Button>
</div>
) : null}
<ScoreAnatomyView
title={t("insights.healthScore.label")}
score={composite.status === "ok" ? composite.value.score : null}
@@ -296,7 +241,7 @@ export function HealthScoreCard({
coverage={composite.coverage}
confidence={composite.status === "ok" ? composite.confidence : null}
provenance={composite.provenance}
method={t("insights.healthScore.method")}
method={null}
insufficient={composite.status !== "ok"}
insufficientNote={
composite.status === "insufficient"
@@ -315,13 +260,6 @@ export function HealthScoreCard({
<div className="border-border/70 bg-card/80 space-y-2 rounded-xl border p-4">
{composite.status === "ok" ? (
<>
<p className="text-foreground text-xs font-semibold">
{t("insights.healthScore.composition", {
pillars: composite.value.composition
.map((id) => t(PILLAR_LABEL_KEY[id]))
.join(", "),
})}
</p>
<p className="text-muted-foreground text-xs">
{t("insights.healthScore.bandSetter", {
pillar: composite.value.bandSetter