diff --git a/src/components/charts/__tests__/trend-card-baseline-alignment.test.tsx b/src/components/charts/__tests__/trend-card-baseline-alignment.test.tsx index 3cb247c7b..e14a1b2af 100644 --- a/src/components/charts/__tests__/trend-card-baseline-alignment.test.tsx +++ b/src/components/charts/__tests__/trend-card-baseline-alignment.test.tsx @@ -11,10 +11,13 @@ import { I18nProvider } from "@/lib/i18n/context"; * Maintainer directive 2026-05-14 named three issues with the 5-tile top row: * * 1. Headings wrapped to two lines at narrow viewports (e.g. "Blood - * Pressure" on Pixel 5 / 393 px / 2-col grid). Locked the heading - * row to a deterministic `h-5` with `whitespace-nowrap` so even an - * unexpectedly long string never breaks the baseline of the value - * row below. + * Pressure" on Pixel 5 / 393 px / 2-col grid), moving the value row. + * The heading row is a deterministic height so the value row keeps + * its baseline. It reserves TWO lines (`h-10`, v1.43.8): pinned to one + * line it could only keep that promise by cutting the label, and long + * German compounds and glucose contexts ended in an ellipsis on every + * dashboard. A label wraps (hyphenated) inside the reserve; the rare + * third line clamps with the full text in `title`. * 2. Trend arrow rendered as a wrapping flex child after the unit — * sometimes wrapped to its own line, sometimes missing entirely * when slope30 was null. Now sits in a fixed `h-4 w-4` slot with @@ -27,8 +30,8 @@ import { I18nProvider } from "@/lib/i18n/context"; * tile in the strip to share the same baseline. * * These tests pin the load-bearing class names so a future refactor - * that drops `h-5` / `items-baseline` / `whitespace-nowrap` triggers a - * red CI before it reaches the dashboard. + * that drops `h-10` / `items-baseline` / `line-clamp-2` triggers a red CI + * before it reaches the dashboard. */ const RISING = { slope: 0.5, direction: "up" as const, confidence: 0.8 }; @@ -53,13 +56,14 @@ describe(" W20a baseline alignment", () => { />, ); - // h-5 is the load-bearing class — keeps the value row below at the - // same y-coordinate regardless of the label string's actual width. - expect(html).toContain("h-5"); - expect(html).toContain('data-slot="trend-card-label"'); + // h-10 is the load-bearing class — keeps the value row below at the + // same y-coordinate whether the label takes one line or two. + expect(html).toMatch( + /class="flex h-10 [^"]*"[^>]*>]*data-slot="trend-card-label"/, + ); }); - it("forbids wrapping on the heading row even with a long label", () => { + it("wraps a long label inside the reserve instead of cutting it", () => { const html = render( W20a baseline alignment", () => { />, ); - expect(html).toContain("whitespace-nowrap"); - expect(html).toContain("truncate"); + const label = html.match( + / { @@ -166,9 +181,10 @@ describe(" W20a baseline alignment", () => { />, ); - // Same heading-row height contract — `h-5` + `whitespace-nowrap`. - expect(html).toContain("h-5"); - expect(html).toContain("whitespace-nowrap"); + // Same heading-row height contract — two reserved label lines. + expect(html).toMatch( + /class="flex h-10 [^"]*"[^>]*>]*data-slot="trend-card-label"/, + ); // Same value-row baseline contract — `items-baseline` + `text-3xl` // + `leading-none`. Identical font geometry across all five tiles // means the digits land at the same y-coordinate inside the grid. diff --git a/src/components/charts/trend-card-skeleton.tsx b/src/components/charts/trend-card-skeleton.tsx index 7527983dc..97bd8e5d6 100644 --- a/src/components/charts/trend-card-skeleton.tsx +++ b/src/components/charts/trend-card-skeleton.tsx @@ -31,10 +31,11 @@ export function TrendCardSkeleton({ className }: { className?: string }) { // legacy empty silhouette reserved. className={cn("min-h-[8rem]", className)} > - {/* Heading row — label + icon (TrendCard pins this row at h-5). */} -
- - + {/* Heading row — label + icon (TrendCard reserves two label lines, + h-10, so the loaded tile does not move the value row). */} +
+ +
{/* Headline value row — `text-3xl leading-none` ≈ 30 px, plus the unit slot, baseline-aligned exactly like the loaded tile. */} diff --git a/src/components/charts/trend-card.tsx b/src/components/charts/trend-card.tsx index 24a13cacb..d131706ac 100644 --- a/src/components/charts/trend-card.tsx +++ b/src/components/charts/trend-card.tsx @@ -206,20 +206,27 @@ export function TrendCard({ return ( - {/* v1.4.25 — single-line discipline + deterministic height for - the heading row so the value row below sits at the same baseline - across every tile. The label is locale-abbreviated upstream (see - `dashboard.*Short` keys) so `truncate` is a layout safeguard - rather than the primary defence against wraps. `h-5` matches - `text-xs leading-5` ≈ 20 px; identical across every tile. */} -
+ {/* The heading row has a deterministic height so the value row below + sits at the same baseline across every tile in the strip. It + reserves two lines, not one: a one-line row could only keep that + promise by cutting the label, and the label is the only thing that + names the figure. An uppercase German compound + ("HERZFREQUENZVARIABILITÄT") or a glucose context ("GLUKOSE, VOR + DEM SCHLAFEN") does not fit a strip tile on one line in any width + the grid hands out, and ended in an ellipsis. `hyphens-auto` splits + a compound at a syllable (driven by the document's `lang`), + `break-words` is the floor, and the rare label that needs a third + line clamps with its full text in `title`. `h-10` is two + `text-xs leading-5` lines; identical across every tile. */} +
{label} - +
{/* v1.4.25 — baseline-aligned value row. The headline value, unit, and trend arrow share a single flex line with