fix(insights): repair the score-anatomy layout and align Coach affordances

The readiness/recovery score-anatomy detail card rendered broken: the
contributor factors were obscured and the method text overlapped the
title and ring on every breakpoint. The provenance explainer no longer
ships a collapsible info glyph; it renders its full method body inline,
but the anatomy view still wrapped it in an absolute top-right box, which
pulled the whole paragraph out of flow and painted it over the centred
title, ring and contributor rows. Render the method caption in normal
flow at the foot of the card so the factors read again and nothing
overlaps.

Move the weekly/monthly review Coach hand-off to the icon-only affordance
pinned top-right, matching the assessment cards instead of a trailing
text button.

Widen the Coach conversation-history page to the standard content width
so the conversation list no longer sits in an artificially narrow column.
This commit is contained in:
Marc-André Bombeck committed 2026-06-29 14:53:53 +02:00
1 parent 4266203d3a
commit 38629d41ed
4 files changed
+60 -55

No files matched your search

+1 -1
View File
@@ -120,7 +120,7 @@ function CoachConversationsBody() {
}
return (
<div className="mx-auto flex h-full min-h-0 w-full max-w-2xl flex-col gap-4 px-4 pt-6 pb-4 md:px-6">
<div className="mx-auto flex h-full min-h-0 w-full max-w-screen-xl flex-col gap-4 px-4 pt-6 pb-4 md:px-6">
<header className="flex flex-col gap-3">
<h1
data-slot="coach-conversations-heading"
@@ -56,9 +56,15 @@ describe("<ScoreAnatomyView>", () => {
expect(html).toContain('data-contributor="respiratory"');
expect(html).toContain('data-present="false"');
expect(html).toContain('data-slot="provenance-explainer-method"');
expect(html).toContain("A blend of deviation components.");
// v1.15.12 F4 — the "keine klinische Bewertung" footer line is removed;
// the non-clinical framing now lives in the inline provenance method.
expect(html).not.toContain('data-slot="score-anatomy-disclaimer"');
// Regression guard: `ProvenanceExplainer` renders its full method body
// inline (the v1.22 refactor dropped the ⓘ glyph), so it must sit in
// normal flow — never pulled out via `absolute top-3 right-3`, which
// painted the whole paragraph over the title, ring and contributor rows.
expect(html).not.toContain("absolute top-3 right-3");
});
it("tints the card + leans the ring hue when a hue is passed (F1)", () => {
@@ -126,24 +126,12 @@ export function ScoreAnatomyView({
: undefined
}
className={cn(
"relative flex flex-col gap-5 rounded-xl border p-5",
"flex flex-col gap-5 rounded-xl border p-5",
hue ? "wellness-detail-card" : "border-border bg-card",
className,
)}
aria-label={typeof title === "string" ? title : undefined}
>
{/* v1.15.12 F5 — the ⓘ explainer is pinned TOP-right at heading height
(it used to trail the card at bottom-right). Absolute so it never
shifts the centred hero title; the trigger keeps its 44px touch
target + popover/sheet behaviour. */}
<div className="absolute top-3 right-3 z-10">
<ProvenanceExplainer
provenance={provenance}
method={method}
standard={standard}
/>
</div>
{/* Hero: title + ring + caption */}
<div className="flex flex-col items-center gap-3 text-center">
<h2
@@ -199,10 +187,18 @@ export function ScoreAnatomyView({
</div>
)}
{/* v1.15.12 F4 — the "Richtwert — keine klinische Bewertung" footer line
is gone; the method + cited standard live behind the provenance
explainer (now top-right, F5), which carries the non-clinical framing
where it belongs. The composition (contributor rows above) stays. */}
{/* The plain-language method caption closes the card, in normal flow.
`ProvenanceExplainer` (v1.22) is no longer a top-right ⓘ glyph — it
renders its full method body inline as a muted caption, so it must
sit in the document flow at the foot of the surface. The earlier
`absolute top-right` wrapper yanked the whole paragraph out of flow
and painted it over the centred title, ring and contributor rows on
every breakpoint; rendering it here keeps the spine readable. */}
<ProvenanceExplainer
provenance={provenance}
method={method}
standard={standard}
/>
</section>
);
}
@@ -11,7 +11,7 @@ import { useAuth } from "@/hooks/use-auth";
import { cn } from "@/lib/utils";
import { Skeleton } from "@/components/ui/skeleton";
import { SectionHeading } from "@/components/insights/section-heading";
import { AskCoachAction } from "@/components/insights/ask-coach-action";
import { AskCoachIconButton } from "@/components/insights/ask-coach-action";
import { ProseBlocks } from "@/components/insights/prose-blocks";
import { apiGet } from "@/lib/api/api-fetch";
@@ -142,29 +142,45 @@ export function PeriodNarrativeCard({
data-period={period}
className={SHELL}
>
{/* Week / month toggle. Calm segmented control, no chart. */}
<div className="flex gap-1 self-start">
{(["week", "month"] as const).map((p) => {
const active = period === p;
return (
<button
key={p}
type="button"
aria-pressed={active}
onClick={() => setPeriod(p)}
className={cn(
"focus-visible:ring-ring inline-flex min-h-11 items-center rounded-md px-2.5 py-1 text-xs font-medium transition-colors focus-visible:ring-2 focus-visible:outline-none",
active
? "bg-muted text-foreground"
: "text-muted-foreground hover:text-foreground",
)}
>
{p === "week"
? t("insights.narrativeWeek")
: t("insights.narrativeMonth")}
</button>
);
})}
{/* Header row: week / month toggle on the left, the icon-only Coach
hand-off pinned top-right. Matches the assessment cards, which carry
the same single `<AskCoachIconButton>` in their header's right slot
(no text label, tooltip + accessible name). The summary spans the
whole picture for the window, so no scope: the default snapshot
reads best. */}
<div className="flex items-start justify-between gap-2">
{/* Calm segmented control, no chart. */}
<div className="flex gap-1">
{(["week", "month"] as const).map((p) => {
const active = period === p;
return (
<button
key={p}
type="button"
aria-pressed={active}
onClick={() => setPeriod(p)}
className={cn(
"focus-visible:ring-ring inline-flex min-h-11 items-center rounded-md px-2.5 py-1 text-xs font-medium transition-colors focus-visible:ring-2 focus-visible:outline-none",
active
? "bg-muted text-foreground"
: "text-muted-foreground hover:text-foreground",
)}
>
{p === "week"
? t("insights.narrativeWeek")
: t("insights.narrativeMonth")}
</button>
);
})}
</div>
<AskCoachIconButton
question={t(
period === "week"
? "insights.coach.seed.periodWeek"
: "insights.coach.seed.periodMonth",
)}
className="shrink-0"
/>
</div>
{/* v1.22 (W6) — real paragraphs via the shared ProseBlocks helper
@@ -184,19 +200,6 @@ export function PeriodNarrativeCard({
user?.timezone,
)}
</p>
{/* v1.21.0 (C4 H2) — hand the period summary to the Coach. It spans
the whole picture for the window, so no scope: the default
snapshot reads best. */}
<div className="flex justify-end">
<AskCoachAction
question={t(
period === "week"
? "insights.coach.seed.periodWeek"
: "insights.coach.seed.periodMonth",
)}
/>
</div>
</div>
</section>
);