mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-08 12:32:43 +00:00
fix(insights): match HealthScore card height to the hero column
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.
This commit is contained in:
1 parent
6f6992c683
commit
155b529def
4 files changed
+111
-4
No files matched your search
@@ -260,6 +260,50 @@ describe("<HealthScoreCard>", () => {
|
||||
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(
|
||||
<HealthScoreCard
|
||||
score={86}
|
||||
band="green"
|
||||
components={baseComponents}
|
||||
delta={null}
|
||||
/>,
|
||||
);
|
||||
const cardOpen = html.match(
|
||||
/<div[^>]*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(
|
||||
<HealthScoreCard
|
||||
score={86}
|
||||
band="green"
|
||||
components={baseComponents}
|
||||
delta={null}
|
||||
/>,
|
||||
);
|
||||
const disclaimerOpen = html.match(
|
||||
/<p[^>]*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(
|
||||
<HealthScoreCard
|
||||
|
||||
@@ -279,4 +279,42 @@ describe("<HeroStrip>", () => {
|
||||
// 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(
|
||||
<HeroStrip
|
||||
briefing={null}
|
||||
now={morningLocal}
|
||||
healthScore={{
|
||||
score: 86,
|
||||
band: "green",
|
||||
components: {
|
||||
bp: { value: 80, weight: 0.3 },
|
||||
weight: { value: 70, weight: 0.2 },
|
||||
mood: { value: 90, weight: 0.2 },
|
||||
compliance: { value: 100, weight: 0.3 },
|
||||
},
|
||||
delta: null,
|
||||
}}
|
||||
/>,
|
||||
);
|
||||
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(<HeroStrip briefing={null} now={morningLocal} />);
|
||||
expect(html).not.toContain("md:items-stretch");
|
||||
});
|
||||
});
|
||||
@@ -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",
|
||||
)}
|
||||
>
|
||||
<div className="flex flex-col gap-3">
|
||||
<div className="flex flex-1 flex-col gap-3">
|
||||
<div className="flex items-center justify-between gap-2">
|
||||
<div className="flex min-w-0 items-center gap-2">
|
||||
<p
|
||||
@@ -545,10 +552,16 @@ export function HealthScoreCard({
|
||||
carries an "Ask the coach" action so the inline button
|
||||
here retires; the parent still receives onAskCoach via
|
||||
other surfaces but the card no longer mounts a duplicate
|
||||
CTA. */}
|
||||
CTA.
|
||||
v1.4.28 R3c-Insights — `mt-auto` pins the disclaimer to
|
||||
the bottom of the stretched card so the score number and
|
||||
sub-bars stay anchored at the top while the recovered
|
||||
vertical space (when the parent column is taller) sits
|
||||
quietly between the provenance accordion and the
|
||||
disclaimer footer. */}
|
||||
<p
|
||||
data-slot="health-score-card-disclaimer"
|
||||
className="text-muted-foreground text-[11px] leading-snug"
|
||||
className="text-muted-foreground mt-auto text-[11px] leading-snug"
|
||||
>
|
||||
{t("insights.healthScore.disclaimer")}
|
||||
</p>
|
||||
|
||||
@@ -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.
|
||||
*/}
|
||||
<div
|
||||
className={cn(
|
||||
"flex flex-col gap-5",
|
||||
healthScore &&
|
||||
"md:flex-row md:items-start md:gap-6 lg:flex-row lg:items-start lg:gap-6",
|
||||
"md:flex-row md:items-stretch md:gap-6 lg:flex-row lg:items-stretch lg:gap-6",
|
||||
)}
|
||||
>
|
||||
<div className="flex min-w-0 flex-1 flex-col gap-5">
|
||||
|
||||
Reference in new issue
Block a user