fix(ui): stop CardHeader drawing a phantom gap without a description

grid-rows-[auto_auto] was unconditional, so a header with a title and
no CardDescription still reserved an empty second row track and drew
the gap-2 for it — 32px instead of the intended 24px between heading
and body on desktop. Make the second row conditional on a description
being present, and widen CardAction to row-span-full so it still
tracks the header's row count when an action lands in a
description-less card.

Also correct two assessment cards that had drifted onto gap-1.5
instead of the sanctioned gap-2 compact tier.
This commit is contained in:
Marc-André Bombeck committed 2026-07-30 15:50:14 +02:00
1 parent a4bb2adcb2
commit 14d0eeeb46
5 files changed
+9 -9

No files matched your search

@@ -176,11 +176,11 @@ describe("assessment presentation contract", () => {
);
expect(cardSource).toContain(
'className="animate-insight-in gap-1.5 py-3 md:py-4"',
'className="animate-insight-in gap-2 py-3 md:py-4"',
);
expect(cardSource).toContain('className="text-foreground text-sm"');
expect(moodSource).toContain(
'className="animate-insight-in gap-1.5 py-3 md:py-4"',
'className="animate-insight-in gap-2 py-3 md:py-4"',
);
expect(moodSource).toContain('data-slot="insight-assessment"');
expect(moodBodySource).toContain(
@@ -188,7 +188,7 @@ describe("<InsightStatusCard>", () => {
expect(html).toContain("py-3");
expect(html).toContain("md:py-4");
}
expect(populated).toContain("gap-1.5");
expect(populated).toContain("gap-2");
expect(populated).toContain("space-y-1.5");
expect(populated).toContain("py-3");
expect(populated).toContain("md:py-4");
@@ -264,7 +264,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-1.5 py-3 md:py-4`) — the sanctioned tight
// Compact card density (`gap-2 py-3 md:py-4`) — 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
@@ -272,7 +272,7 @@ 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-1.5 py-3 md:py-4"
className="animate-insight-in gap-2 py-3 md:py-4"
>
<CardHeader>
{/* v1.11.5 — the top-right "cached" label was removed: it surfaced
@@ -285,10 +285,10 @@ export function MoodInsightsSections({
<Card
aria-live="polite"
data-slot="insight-assessment"
// v1.13.1 — match the canonical `gap-1.5` + `pb-1` heading-to-body
// v1.13.1 — match the canonical `gap-2` + `pb-1` heading-to-body
// rhythm the other assessment cards use, so the heading sits tight
// above its body instead of floating ~16-24 px above it.
className="animate-insight-in gap-1.5 py-3 md:py-4"
className="animate-insight-in gap-2 py-3 md:py-4"
>
<CardHeader>
<TileHeader
+2 -2
View File
@@ -33,7 +33,7 @@ function CardHeader({ className, ...props }: React.ComponentProps<"div">) {
<div
data-slot="card-header"
className={cn(
"@container/card-header grid auto-rows-min grid-rows-[auto_auto] items-start gap-2 px-4 has-data-[slot=card-action]:grid-cols-[1fr_auto] md:px-6 [.border-b]:pb-4 md:[.border-b]:pb-6",
"@container/card-header grid auto-rows-min items-start gap-2 px-4 has-data-[slot=card-action]:grid-cols-[1fr_auto] has-data-[slot=card-description]:grid-rows-[auto_auto] md:px-6 [.border-b]:pb-4 md:[.border-b]:pb-6",
className,
)}
{...props}
@@ -66,7 +66,7 @@ function CardAction({ className, ...props }: React.ComponentProps<"div">) {
<div
data-slot="card-action"
className={cn(
"col-start-2 row-span-2 row-start-1 self-start justify-self-end",
"col-start-2 row-span-full row-start-1 self-start justify-self-end",
className,
)}
{...props}