From d83220a4e08a3630bcf8ee14d5c6e67ddbcd3d39 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andr=C3=A9=20Bombeck?= Date: Sun, 5 Jul 2026 13:27:30 +0200 Subject: [PATCH] feat(insights): assessment and prose readability pass - assessments render in full; the three-line clamp and its show-more toggle are gone - AI prose (assessment, briefing, explainers, usual-range note) reads in the regular foreground; muted stays reserved for meta lines - the usual-range note becomes a proper card with the shared TileHeader (icon + title, labelled in all six locales) - the metric-page heading + explainer indent to the cards' text edge - the health-score card drops its Ask-the-Coach corner button --- messages/de.json | 2 +- messages/en.json | 2 +- messages/es.json | 2 +- messages/fr.json | 2 +- messages/it.json | 2 +- messages/pl.json | 2 +- .../insights/derived/coach-read-strip.tsx | 46 +++++-------- src/components/insights/health-score-card.tsx | 11 --- src/components/insights/hero-strip.tsx | 2 +- .../insights/insight-status-card.tsx | 67 ++----------------- src/components/insights/sub-page-shell.tsx | 7 +- 11 files changed, 36 insertions(+), 109 deletions(-) diff --git a/messages/de.json b/messages/de.json index edc5f2450..28604c9b1 100644 --- a/messages/de.json +++ b/messages/de.json @@ -2353,7 +2353,7 @@ "tokensUsedWithModel": "{count} Tokens · {model}", "askAboutThis": "Den Coach dazu fragen", "readStrip": { - "label": "Coach-Einschätzung", + "label": "Üblicher Bereich", "baselineWithin": "Dein üblicher Bereich liegt bei {low}–{high} {unit}; heute liegt {value} darin.", "baselineAbove": "Dein üblicher Bereich liegt bei {low}–{high} {unit}; heute liegt {value} darüber.", "baselineBelow": "Dein üblicher Bereich liegt bei {low}–{high} {unit}; heute liegt {value} darunter.", diff --git a/messages/en.json b/messages/en.json index 58ab35eeb..df45fbd25 100644 --- a/messages/en.json +++ b/messages/en.json @@ -2353,7 +2353,7 @@ "tokensUsedWithModel": "{count} tokens · {model}", "askAboutThis": "Ask the coach about this", "readStrip": { - "label": "Coach read", + "label": "Usual range", "baselineWithin": "Your usual range is {low}–{high} {unit}; today's {value} sits within it.", "baselineAbove": "Your usual range is {low}–{high} {unit}; today's {value} sits above it.", "baselineBelow": "Your usual range is {low}–{high} {unit}; today's {value} sits below it.", diff --git a/messages/es.json b/messages/es.json index eca0a43ce..35f95049a 100644 --- a/messages/es.json +++ b/messages/es.json @@ -2353,7 +2353,7 @@ "tokensUsedWithModel": "{count} tokens · {model}", "askAboutThis": "Pregúntale al coach sobre esto", "readStrip": { - "label": "Lectura del Coach", + "label": "Rango habitual", "baselineWithin": "Tu rango habitual es {low}–{high} {unit}; hoy {value} se sitúa dentro de él.", "baselineAbove": "Tu rango habitual es {low}–{high} {unit}; hoy {value} se sitúa por encima.", "baselineBelow": "Tu rango habitual es {low}–{high} {unit}; hoy {value} se sitúa por debajo.", diff --git a/messages/fr.json b/messages/fr.json index f3dc0a915..92682f7a4 100644 --- a/messages/fr.json +++ b/messages/fr.json @@ -2353,7 +2353,7 @@ "tokensUsedWithModel": "{count} jetons · {model}", "askAboutThis": "Interroger le coach à ce sujet", "readStrip": { - "label": "Lecture du Coach", + "label": "Plage habituelle", "baselineWithin": "Ta plage habituelle est de {low}–{high} {unit} ; aujourd'hui, {value} s'y situe.", "baselineAbove": "Ta plage habituelle est de {low}–{high} {unit} ; aujourd'hui, {value} se situe au-dessus.", "baselineBelow": "Ta plage habituelle est de {low}–{high} {unit} ; aujourd'hui, {value} se situe en dessous.", diff --git a/messages/it.json b/messages/it.json index bd725b9e4..6a20cc358 100644 --- a/messages/it.json +++ b/messages/it.json @@ -2353,7 +2353,7 @@ "tokensUsedWithModel": "{count} token · {model}", "askAboutThis": "Chiedi al coach", "readStrip": { - "label": "Lettura del Coach", + "label": "Intervallo abituale", "baselineWithin": "Il tuo intervallo abituale è {low}–{high} {unit}; oggi {value} rientra al suo interno.", "baselineAbove": "Il tuo intervallo abituale è {low}–{high} {unit}; oggi {value} si colloca al di sopra.", "baselineBelow": "Il tuo intervallo abituale è {low}–{high} {unit}; oggi {value} si colloca al di sotto.", diff --git a/messages/pl.json b/messages/pl.json index 45153ba77..179663f05 100644 --- a/messages/pl.json +++ b/messages/pl.json @@ -2353,7 +2353,7 @@ "tokensUsedWithModel": "{count} tokenów · {model}", "askAboutThis": "Zapytaj trenera o to", "readStrip": { - "label": "Ocena Coacha", + "label": "Typowy zakres", "baselineWithin": "Twój zwykły zakres to {low}–{high} {unit}; dzisiejsza wartość {value} mieści się w nim.", "baselineAbove": "Twój zwykły zakres to {low}–{high} {unit}; dzisiejsza wartość {value} jest powyżej niego.", "baselineBelow": "Twój zwykły zakres to {low}–{high} {unit}; dzisiejsza wartość {value} jest poniżej niego.", diff --git a/src/components/insights/derived/coach-read-strip.tsx b/src/components/insights/derived/coach-read-strip.tsx index 2d89ca7db..6e5eb68aa 100644 --- a/src/components/insights/derived/coach-read-strip.tsx +++ b/src/components/insights/derived/coach-read-strip.tsx @@ -8,7 +8,7 @@ import { useMounted } from "@/hooks/use-mounted"; import { useTranslations } from "@/lib/i18n/context"; import { queryKeys } from "@/lib/query-keys"; import { apiGet } from "@/lib/api/api-fetch"; -import { cn } from "@/lib/utils"; +import { TileHeader } from "@/components/insights/tile-header"; import type { CoachReadStripData } from "@/lib/insights/derived/coach-read-shape"; /** @@ -109,37 +109,27 @@ export function CoachReadStrip({ // fires defensively.) if (!baselineLine && !driverLine) return null; + // Standard card anatomy — the same TileHeader-led shape as every other + // insights card (foreground icon + title), instead of the former inline + // mini-icon strip that read as chrome outside the card system. Body prose + // in the regular foreground; muted stays reserved for meta lines. return (
-
-
); diff --git a/src/components/insights/health-score-card.tsx b/src/components/insights/health-score-card.tsx index 21a657341..9eab66de4 100644 --- a/src/components/insights/health-score-card.tsx +++ b/src/components/insights/health-score-card.tsx @@ -13,7 +13,6 @@ import { import { useTranslations } from "@/lib/i18n/context"; import { cn } from "@/lib/utils"; import { HealthScoreDeltaExplainer } from "./health-score-delta-explainer"; -import { AskCoachAction } from "./ask-coach-action"; /** * v1.4.20 B5 — Personal Health Score panel. @@ -703,16 +702,6 @@ export function HealthScoreCard({ {/* v1.18.6 (DISC-01) — the "indicative, not a clinical assessment" card disclaimer is removed; the one-time onboarding acknowledgment now covers the not-a-diagnosis framing app-wide. */} - - {/* v1.21.0 (C4 H2) — the score is a whole-picture composite, so the - hand-off opens the Coach against the default snapshot (no scope) - seeded with the actual number. Mounts at the foot of the card as - a single discreet entry, not a primary CTA. */} -
- -
); diff --git a/src/components/insights/hero-strip.tsx b/src/components/insights/hero-strip.tsx index 8248d19c8..c145ed62c 100644 --- a/src/components/insights/hero-strip.tsx +++ b/src/components/insights/hero-strip.tsx @@ -300,7 +300,7 @@ export function HeroStrip({ spacing (still plain text children, no markdown). */}
diff --git a/src/components/insights/insight-status-card.tsx b/src/components/insights/insight-status-card.tsx index d3793e74e..43cc119af 100644 --- a/src/components/insights/insight-status-card.tsx +++ b/src/components/insights/insight-status-card.tsx @@ -1,6 +1,5 @@ "use client"; -import { useEffect, useRef, useState } from "react"; import Link from "next/link"; import { Card, CardContent, CardHeader } from "@/components/ui/card"; @@ -287,67 +286,13 @@ export function InsightStatusCard({ * overflow, and re-measures on resize / font load via `ResizeObserver`. */ function StatusBody({ text }: { text: string }) { - const { t } = useTranslations(); - const [expanded, setExpanded] = useState(false); - const [overflows, setOverflows] = useState(false); - const wrapperRef = useRef(null); - - // v1.22 (W6) — the assessment now renders through the shared `ProseBlocks` - // helper (real paragraphs). The 3-line clamp therefore moves from the single - // `

` to the BLOCK CONTAINER: a max-height + overflow-hidden when collapsed, - // measured on the wrapper node so multi-paragraph text doesn't re-collapse. - // When the text fits, `scrollHeight` equals `clientHeight`; an overflow means - // the clamp is hiding content and the toggle earns its place. Gated on - // `!expanded` so the comparison reads against the clamped element. - useEffect(() => { - if (expanded) return; - const node = wrapperRef.current; - if (!node) return; - - const measure = () => { - setOverflows(node.scrollHeight > node.clientHeight + 1); - }; - measure(); - - if (typeof ResizeObserver === "undefined") return; - const observer = new ResizeObserver(measure); - observer.observe(node); - return () => observer.disconnect(); - // Re-measure whenever the source text changes (a fresh assessment) or - // the user collapses the card again. - }, [text, expanded]); - + // The assessment shows in full — the 3-line clamp + "show more" toggle + // hid the prose behind an extra tap for no gain on a card whose whole + // point is the text. Body prose reads in the regular foreground; muted + // stays reserved for meta lines (timestamps, captions). return ( -

-
- -
- {/* Only render the toggle on a genuine overflow. When the full text - fits inside three lines there is nothing more to show, so no - affordance is painted. */} - {(overflows || expanded) && ( - - )} +
+
); } diff --git a/src/components/insights/sub-page-shell.tsx b/src/components/insights/sub-page-shell.tsx index b3658cd3d..ece0a44a0 100644 --- a/src/components/insights/sub-page-shell.tsx +++ b/src/components/insights/sub-page-shell.tsx @@ -218,7 +218,10 @@ export function SubPageShell({
{/* v1.8.7.1 — back-nav leads the page, above the heading. */} {backLink} -
+ {/* The heading + explainer indent to the cards' inner text edge + (`px-6`, the Card content padding), so the page head reads as + part of the card column instead of hanging outside it. */} +
{/* v1.8.6 — the heading row pins the Coach icon top-right while the title / nudge / badge wrap together on the left. @@ -337,7 +340,7 @@ export function SubPageShell({ // string was removed.

{t(`insights.subPage.explainer.${explainerMetric}Body`)}