From 155b529defe92cbbbb5151156bfb4b6ecb0ad549 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andr=C3=A9=20Bombeck?= Date: Sat, 16 May 2026 01:41:36 +0200 Subject: [PATCH] fix(insights): match HealthScore card height to the hero column MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The HealthScore card paints 75-110 px shorter than the action+prompts column to its left at md+ — the bottom edge of the card lands a full chip-row above the suggested-prompts strip's last entry. The two columns sit in a flex row but the row was anchoring children with `items-start`, which lets the right column collapse to its content height even though the left column's chip-strip wraps to two or three rows on desktop. The fix is two-sided: - The hero row flips from `items-start` to `items-stretch` whenever the HealthScore panel mounts. The class only applies on the conditional branch so the no-score layout keeps the same vertical rhythm it had. - The card wrapper opts into the stretched shell with `flex h-full flex-col`; the existing inner content block becomes `flex-1 flex flex-col gap-3` and the disclaimer footer gets `mt-auto`. The score number and sub-bars stay anchored at the top, the disclaimer pins to the bottom, and the recovered vertical space sits quietly between the provenance accordion and the disclaimer footer rather than spilling under the disclaimer line. FB-H3 — the "Wochenbericht erstellen" button and the WeeklyReportBanner were both retired in the R3b weekly-report scope-reduction pass; the hero action row is already Coach-only and nothing in this commit revisits that surface. Tests pin the row's `md:items-stretch` / `lg:items-stretch` classes, the wrapper's `h-full flex flex-col` opt-in, and the disclaimer's `mt-auto` anchor — DOM-class assertions, no brittle pixel-height checks. Tests on the no-score branch keep the older `items-start` absence pinned so a future refactor can't blanket-apply the stretch contract on layouts that have nothing to stretch toward. --- .../__tests__/health-score-card.test.tsx | 44 +++++++++++++++++++ .../insights/__tests__/hero-strip.test.tsx | 38 ++++++++++++++++ src/components/insights/health-score-card.tsx | 19 ++++++-- src/components/insights/hero-strip.tsx | 14 +++++- 4 files changed, 111 insertions(+), 4 deletions(-) diff --git a/src/components/insights/__tests__/health-score-card.test.tsx b/src/components/insights/__tests__/health-score-card.test.tsx index 723b438dd..a89e0c868 100644 --- a/src/components/insights/__tests__/health-score-card.test.tsx +++ b/src/components/insights/__tests__/health-score-card.test.tsx @@ -260,6 +260,50 @@ describe("", () => { expect(progressMatch?.[1]).toBe("100%"); }); + // ── v1.4.28 R3c-Insights — equal-height contract (FB-H1/H2) ─────── + it("opts the wrapper into the parent's flex-stretch row via h-full + flex-col", () => { + // The hero strip's row now uses `items-stretch`; the card has to + // declare `h-full flex flex-col` so the stretched shell actually + // affects its rendered height. Pin both classes so a refactor + // can't silently drop one. + const html = ssr( + , + ); + const cardOpen = html.match( + /]*data-slot="health-score-card"[^>]*>/, + ); + expect(cardOpen).not.toBeNull(); + expect(cardOpen?.[0]).toContain("h-full"); + expect(cardOpen?.[0]).toContain("flex"); + expect(cardOpen?.[0]).toContain("flex-col"); + }); + + it("pins the disclaimer to the bottom of the stretched card via mt-auto", () => { + // Without `mt-auto` the disclaimer rides directly under the + // provenance accordion and leaves the recovered space below the + // disclaimer instead of between the body and the footer. The + // FB-H2 contract is "score number on top, disclaimer on bottom, + // recovered space in the middle". + const html = ssr( + , + ); + const disclaimerOpen = html.match( + /]*data-slot="health-score-card-disclaimer"[^>]*>/, + ); + expect(disclaimerOpen).not.toBeNull(); + expect(disclaimerOpen?.[0]).toContain("mt-auto"); + }); + it("exposes the progressbar role + ARIA values for screen readers", () => { const html = ssr( ", () => { // is present so the panel sits beside the title block on desktop. expect(html).toContain("lg:flex-row"); }); + + // ── v1.4.28 R3c-Insights — equal-height contract (FB-H1/H2) ─────── + it("stretches the row's cross-axis when the HealthScore card mounts (md+/lg+)", () => { + // Per Inv-4 the right column painted 75-110 px shorter than the + // left column on desktop. Switching the parent flex row from + // `items-start` to `items-stretch` gives the card a stretched + // shell to grow into. The class is load-bearing for FB-H1/H2 — + // if a future refactor reverts to `items-start` the height gap + // returns. + const html = render( + , + ); + expect(html).toContain("md:items-stretch"); + expect(html).toContain("lg:items-stretch"); + expect(html).not.toContain("md:items-start"); + }); + + it("does NOT stretch the row when healthScore is omitted", () => { + // Pin the negative case so a future refactor can't blanket-apply + // the stretch contract on the no-score layout (no right column + // means nothing to stretch toward). + const html = render(); + expect(html).not.toContain("md:items-stretch"); + }); }); diff --git a/src/components/insights/health-score-card.tsx b/src/components/insights/health-score-card.tsx index be8221d8b..4944b2024 100644 --- a/src/components/insights/health-score-card.tsx +++ b/src/components/insights/health-score-card.tsx @@ -241,9 +241,16 @@ export function HealthScoreCard({ // (~36 % at md, ~40 % at xl) but cede headroom when the parent // narrows. "w-full md:basis-[22rem] md:shrink-0 md:grow-0 xl:basis-[26rem]", + // v1.4.28 R3c-Insights — stretch to match the parent's + // `items-stretch` row height (FB-H1/H2). `h-full` + an inner + // flex column lets the disclaimer ride to the bottom with + // `mt-auto` so the headline number stays at the top and the + // recovered vertical space pads through the middle rather + // than under the disclaimer. + "flex h-full flex-col", )} > -
+

{t("insights.healthScore.disclaimer")}

diff --git a/src/components/insights/hero-strip.tsx b/src/components/insights/hero-strip.tsx index 52a270a8a..7d2f936f6 100644 --- a/src/components/insights/hero-strip.tsx +++ b/src/components/insights/hero-strip.tsx @@ -151,11 +151,23 @@ export function HeroStrip({ * `lg:` modifiers alongside so existing snapshot/assertion tests * that grep for `lg:flex-row` continue to find it. */} + {/* + * v1.4.28 R3c-Insights — switch the row's cross-axis alignment + * from `items-start` to `items-stretch` so the right-column + * HealthScore card grows to match the left column's natural + * height (greeting + subtitle + baseline meta + action row + + * suggested-prompts strip). Per Inv-4 the card was painting + * 75-110 px shorter than the left column on desktop; the + * stretch contract pulls the card's bottom edge down to the + * "Wirkt mein Medikament?" chip. The card itself owns + * `h-full` + `mt-auto` on its disclaimer to redistribute the + * recovered space without visual jank. + */}