From 3cc3829fba9e45763562fd6c4392ca0f7c068f49 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andr=C3=A9=20Bombeck?= Date: Tue, 20 Oct 2026 13:49:25 +0200 Subject: [PATCH] fix(ui): card density is a Card prop, and compact is compact on desktop Nineteen cards spelled the compact tier by hand as gap-2 py-3 md:py-4, which left the base md:gap-6 in place: a compact card opened a 24 px gap between header and body on a desktop. Card takes density="compact" now and pins the gap on both breakpoints; every hand-written compact card, the priority card, the document card and the sample briefing move onto it, and a test refuses the class list on a Card. The precision guard on the steps page now reads rendered text only: the page header's grid template (minmax(0,1fr)) matched its pattern. --- .../__tests__/assessment-spine-order.test.ts | 10 ++-- src/app/insights/blood-pressure/page.tsx | 2 +- src/app/insights/catalog/page.tsx | 6 +-- src/app/insights/medications/page.tsx | 2 +- src/components/ai/ai-consent-request.tsx | 2 +- .../body-sites/body-sites-section.tsx | 4 +- src/components/daily/priority-card.tsx | 2 +- src/components/day/since-last-visit.tsx | 4 +- src/components/documents/document-card.tsx | 3 +- .../__tests__/visits-section.test.tsx | 4 +- src/components/encounters/visit-card.tsx | 2 +- .../integer-metric-precision-guard.test.tsx | 4 +- src/components/insights/ai-setup-hint.tsx | 2 +- .../insights/derived/coach-read-strip.tsx | 4 +- src/components/insights/glp1-plateau-note.tsx | 2 +- .../insights/insight-status-card.tsx | 13 ++--- src/components/insights/metric-stat-strip.tsx | 6 +-- .../insights/metric-target-summary.tsx | 2 +- .../insights/mood/mood-insights-sections.tsx | 3 +- .../insights/mood/mood-prognosis-card.tsx | 2 +- .../insights/workout-detail/day-context.tsx | 2 +- .../onboarding/sample-briefing-card.tsx | 3 +- .../practitioners/practitioner-list.tsx | 2 +- src/components/timeline/readiness-card.tsx | 2 +- .../timeline/timeline-chronicle.tsx | 2 +- .../ui/__tests__/card-density.test.tsx | 48 +++++++++++++++++++ src/components/ui/card.tsx | 20 +++++++- 27 files changed, 111 insertions(+), 47 deletions(-) create mode 100644 src/components/ui/__tests__/card-density.test.tsx diff --git a/src/app/insights/__tests__/assessment-spine-order.test.ts b/src/app/insights/__tests__/assessment-spine-order.test.ts index 49f601778..e71f3cabc 100644 --- a/src/app/insights/__tests__/assessment-spine-order.test.ts +++ b/src/app/insights/__tests__/assessment-spine-order.test.ts @@ -181,13 +181,11 @@ describe("assessment presentation contract", () => { "utf8", ); - expect(cardSource).toContain( - 'className="animate-insight-in gap-2 py-3 md:py-4"', - ); + expect(cardSource).toContain('density="compact"'); + expect(cardSource).toContain('className="animate-insight-in"'); expect(cardSource).toContain('className="text-foreground text-sm"'); - expect(moodSource).toContain( - 'className="animate-insight-in gap-2 py-3 md:py-4"', - ); + expect(moodSource).toContain('density="compact"'); + expect(moodSource).toContain('className="animate-insight-in"'); expect(moodSource).toContain('data-slot="insight-assessment"'); expect(moodBodySource).toContain( 'className="text-foreground mb-2 text-sm"', diff --git a/src/app/insights/blood-pressure/page.tsx b/src/app/insights/blood-pressure/page.tsx index a6772ce9c..43186b0a4 100644 --- a/src/app/insights/blood-pressure/page.tsx +++ b/src/app/insights/blood-pressure/page.tsx @@ -204,7 +204,7 @@ export default function InsightsBlutdruckPage() { // edge, ~16–24 px left of every neighbouring card's text) — and // foreground, not muted: multi-sentence explainer prose is // content, muted stays reserved for meta. - +

{t("insights.subPage.bpDerived.pulsePressure", { diff --git a/src/app/insights/catalog/page.tsx b/src/app/insights/catalog/page.tsx index 88425d5f7..805a7bdfe 100644 --- a/src/app/insights/catalog/page.tsx +++ b/src/app/insights/catalog/page.tsx @@ -351,11 +351,7 @@ export default function MetricCatalogPage() { // it ever probes for rows, so a module-off account never saw // the in-context enable CTA; this row is the fix). return ( - + +

diff --git a/src/components/ai/ai-consent-request.tsx b/src/components/ai/ai-consent-request.tsx index 81d5d24fe..09f26ba45 100644 --- a/src/components/ai/ai-consent-request.tsx +++ b/src/components/ai/ai-consent-request.tsx @@ -138,7 +138,7 @@ export function AiConsentRequest({ }; return ( - + @@ -326,7 +326,7 @@ function ConditionRow({ const site = bodySiteText(t, condition.bodySite, condition.laterality); return ( diff --git a/src/components/daily/priority-card.tsx b/src/components/daily/priority-card.tsx index e958ee956..6f47ec2c1 100644 --- a/src/components/daily/priority-card.tsx +++ b/src/components/daily/priority-card.tsx @@ -163,9 +163,9 @@ export function PriorityCard({ + + since the last visit", () => { html.indexOf("since-last-visit-"), ), ); - expect(card).toMatch( - /^
diff --git a/src/components/insights/__tests__/integer-metric-precision-guard.test.tsx b/src/components/insights/__tests__/integer-metric-precision-guard.test.tsx index 1e56b9872..a7e4ad6c6 100644 --- a/src/components/insights/__tests__/integer-metric-precision-guard.test.tsx +++ b/src/components/insights/__tests__/integer-metric-precision-guard.test.tsx @@ -173,7 +173,9 @@ describe("integer-metric precision guard", () => { const html = render(); expect(html).toContain('data-slot="metric-stat-strip"'); - expect(html).not.toMatch(FRACTIONAL_GERMAN); + // Class lists carry grid templates such as `minmax(0,1fr)`; only the + // rendered text is under test. + expect(html.replace(/class="[^"]*"/g, "")).not.toMatch(FRACTIONAL_GERMAN); // The numbers themselves survive, thousands-grouped and whole. expect(html).toContain("11.762"); expect(html).toContain("104"); diff --git a/src/components/insights/ai-setup-hint.tsx b/src/components/insights/ai-setup-hint.tsx index cf4f3e008..89bdb7a9b 100644 --- a/src/components/insights/ai-setup-hint.tsx +++ b/src/components/insights/ai-setup-hint.tsx @@ -101,7 +101,7 @@ export function AiSetupHint({ } return ( - + + + @@ -172,7 +172,7 @@ export function InsightStatusCard({ // Compact card density — the same tier the populated / preparing / // empty states use, so the heading and the first skeleton line sit // on the same baseline across every assessment-card state. - className="gap-2 py-3 md:py-4" + density="compact" >
@@ -209,7 +209,7 @@ export function InsightStatusCard({ aria-live="polite" role="status" data-testid={`insight-status-card-${terminalKind}`} - className="gap-2 py-3 md:py-4" + density="compact" > @@ -223,7 +223,7 @@ export function InsightStatusCard({ if (!displayText) { return ( - + @@ -247,7 +247,7 @@ export function InsightStatusCard({ // v1.12.2 — stable hook for the spine guard test, which asserts the // assessment is the LAST content block on every bespoke metric page. data-slot="insight-assessment" - // Compact card density (`gap-2 py-3 md:py-4`) — the sanctioned tight + // Compact card density — the sanctioned tight // tier, so the assessment shares one header→body rhythm with the // target summary, the read strip, and the stat strip on the same // spine. Earlier bespoke values (`gap-1.5` + `md:py-5`) predate the @@ -255,7 +255,8 @@ export function InsightStatusCard({ // lands on every status variant so the gap is consistent across // states. (The one-time purple left accent stays dropped — the plain // card border carries enough separation.) - className="animate-insight-in gap-2 py-3 md:py-4" + density="compact" + className="animate-insight-in" > {/* v1.11.5 — the top-right "cached" label was removed: it surfaced diff --git a/src/components/insights/metric-stat-strip.tsx b/src/components/insights/metric-stat-strip.tsx index 0a36f349b..34d3142b3 100644 --- a/src/components/insights/metric-stat-strip.tsx +++ b/src/components/insights/metric-stat-strip.tsx @@ -286,7 +286,7 @@ export function MetricStatStrip({ data-multi-series="true" role="group" aria-label={groupLabel ?? t("insights.subPage.stats.label")} - className="gap-2 py-3 md:py-4" + density="compact" >
@@ -319,7 +319,7 @@ export function MetricStatStrip({ ? `${t("insights.subPage.stats.label")}: ${seriesLabel}` : t("insights.subPage.stats.label") } - className="gap-2 py-3 md:py-4" + density="compact" >