Merge v1.25.3: v1253-insights-ui

This commit is contained in:
Marc-André Bombeck committed 2026-06-29 15:30:32 +02:00
commit 0e21210263
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>
);