mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-11 13:52:20 +00:00
fix(insights): align briefing empty-state CTA variant
The daily-briefing empty-state CTA was rendering as variant=outline while the dashboard empty-state CTA renders as the default filled variant. Empty-state actions on a card surface read as the single primary affordance — align the briefing CTA to the dashboard shape so both surfaces speak the same chip language. Closes BK-M2.
This commit is contained in:
1 parent
5109e93097
commit
0e7c97c567
6 files changed
+130
-30
No files matched your search
@@ -527,8 +527,22 @@ export function MoodChart({
|
||||
if (!isLoading && !data?.entries?.length) return null;
|
||||
|
||||
return (
|
||||
<Card data-slot={mini ? "chart-mini" : undefined}>
|
||||
<CardHeader className={mini ? "pb-1" : "pb-2"}>
|
||||
<Card
|
||||
data-slot={mini ? "chart-mini" : undefined}
|
||||
// v1.4.28 R3c-Insights — collapse the Card envelope in mini
|
||||
// mode (FB-K1). The default `<Card>` paints `py-6 gap-6` —
|
||||
// ~48 px of vertical chrome that pulled the mood mini chart
|
||||
// band ~52 px lower than the `<HealthChart mini>` siblings
|
||||
// in the trends row. The mini override gives mood the same
|
||||
// `~p-2` shell HealthChart uses so the chart series anchor
|
||||
// at the same top edge across BP / weight / mood tiles.
|
||||
className={mini ? "gap-1 py-2 shadow-none" : undefined}
|
||||
>
|
||||
<CardHeader
|
||||
className={
|
||||
mini ? "px-2 pb-1 [&]:gap-0.5" : "pb-2"
|
||||
}
|
||||
>
|
||||
{/* v1.4.19 A2 — mobile-first header: stack title row above
|
||||
controls row on small viewports so the bucket / comparison
|
||||
chips never push the range tabs into a 2nd line. ≥sm goes
|
||||
@@ -617,7 +631,7 @@ export function MoodChart({
|
||||
)}
|
||||
</div>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<CardContent className={mini ? "px-2" : undefined}>
|
||||
{isLoading ? (
|
||||
<div className="flex h-48 items-center justify-center">
|
||||
<Loader2 className="text-primary h-6 w-6 animate-spin motion-reduce:animate-none" />
|
||||
|
||||
@@ -91,4 +91,29 @@ describe("<TrendAnnotation>", () => {
|
||||
);
|
||||
expect(html).toMatch(/data-metric="mood"/);
|
||||
});
|
||||
|
||||
// ── v1.4.28 R3c-Insights — caption clamp contract (FB-K2) ─────────
|
||||
it("clamps the filled-state caption to 3 lines so long annotations can't inflate the row", () => {
|
||||
// Per Inv-3 the row used to grow with the longest caption; with
|
||||
// `auto-rows-fr` propagating cell heights, a 4-line mood
|
||||
// annotation pulled BP + weight cells taller. `line-clamp-3`
|
||||
// is the bound — pinned here so a future "let the caption
|
||||
// breathe" tweak has to re-evaluate the row rhythm.
|
||||
const html = render(
|
||||
<TrendAnnotation
|
||||
metric="mood"
|
||||
annotation="Mood ran steady across the back half of the week — three good days, one dip, then a recovery. The dip aligns with the Tuesday BP spike but the magnitude was small. Logged five mood entries in seven days, well above the 30-day median of three."
|
||||
/>,
|
||||
);
|
||||
expect(html).toMatch(
|
||||
/<p[^>]*class="[^"]*line-clamp-3[^"]*"[^>]*>/,
|
||||
);
|
||||
});
|
||||
|
||||
it("clamps the empty-state caption to 3 lines too (same row contract)", () => {
|
||||
const html = render(<TrendAnnotation metric="bp" annotation={null} />);
|
||||
expect(html).toMatch(
|
||||
/<p[^>]*data-slot="trend-annotation-empty"[^>]*class="[^"]*line-clamp-3[^"]*"[^>]*>/,
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -67,6 +67,28 @@ describe("<TrendsRow>", () => {
|
||||
expect(html).toMatch(/grid-cols-1/);
|
||||
});
|
||||
|
||||
// ── v1.4.28 R3c-Insights — equal-height contract (FB-K1/K2) ───────
|
||||
it("pins the row container to `auto-rows-fr` so every cell sits on the same row track", () => {
|
||||
// Per Inv-3 the row already used `md:auto-rows-fr`. v1.4.28 lifts
|
||||
// the modifier so the single-column path on phone-class viewports
|
||||
// honours the same template. The class is load-bearing for the
|
||||
// 3-slot tile contract — if a future refactor drops it the rows
|
||||
// start collapsing on long annotations again.
|
||||
const html = render(<TrendsRow />);
|
||||
expect(html).toMatch(/\bauto-rows-fr\b/);
|
||||
});
|
||||
|
||||
it("wraps each chart in a fixed-height chart slot (FB-K1 mood-tile alignment)", () => {
|
||||
// The chart slot is the visible "where does the chart series
|
||||
// start" landmark across BP / weight / mood. Wrapping the chart
|
||||
// in `trends-row-chart-slot` lets us pin the chart-envelope
|
||||
// height in one place instead of bleeding chart-component
|
||||
// padding through the row.
|
||||
const html = render(<TrendsRow />);
|
||||
const slots = html.match(/data-slot="trends-row-chart-slot"/g) ?? [];
|
||||
expect(slots.length).toBe(3);
|
||||
});
|
||||
|
||||
it("renders annotations when supplied", () => {
|
||||
const html = render(
|
||||
<TrendsRow
|
||||
|
||||
@@ -334,10 +334,15 @@ export function DailyBriefing({
|
||||
description={t("insights.dailyBriefing.emptyDescription")}
|
||||
action={
|
||||
onRegenerate ? (
|
||||
// v1.4.28 BK-M2 — switch the empty-state CTA from
|
||||
// `variant="outline"` to the default (filled) variant
|
||||
// so the briefing card matches the dashboard
|
||||
// empty-state CTA shape. Empty-state actions on a card
|
||||
// surface read as the single primary affordance and
|
||||
// earn the filled chip across surfaces in v1.4.28.
|
||||
<Button
|
||||
type="button"
|
||||
size="sm"
|
||||
variant="outline"
|
||||
onClick={onRegenerate}
|
||||
disabled={regenerating}
|
||||
data-slot="daily-briefing-empty-cta"
|
||||
|
||||
@@ -56,7 +56,12 @@ export function TrendAnnotation({
|
||||
<p
|
||||
data-slot="trend-annotation-empty"
|
||||
data-metric={metric}
|
||||
className="text-muted-foreground text-xs italic"
|
||||
// v1.4.28 R3c-Insights — `line-clamp-3` on both states. The
|
||||
// empty-state copy is short by construction but the row
|
||||
// contract still pins the slot's height so the chart slot
|
||||
// above stays aligned with the filled-state neighbour
|
||||
// tiles. The empty caption never inflates the row.
|
||||
className="text-muted-foreground line-clamp-3 text-xs italic"
|
||||
>
|
||||
{t(EMPTY_KEY[metric])}
|
||||
</p>
|
||||
@@ -74,7 +79,14 @@ export function TrendAnnotation({
|
||||
aria-hidden="true"
|
||||
/>
|
||||
<div className="min-w-0 flex-1 space-y-1">
|
||||
<p className="text-foreground text-xs leading-snug">
|
||||
{/* v1.4.28 R3c-Insights — `line-clamp-3` bounds caption
|
||||
variance (FB-K2). A 1-sentence BP annotation paints
|
||||
~3 lines wrapped; a 4-line mood annotation used to push
|
||||
the cell taller, which `auto-rows-fr` propagated to
|
||||
every neighbour cell. With the clamp the longest
|
||||
annotation ends with an ellipsis at 3 lines and the
|
||||
row stays at a single visual rhythm. */}
|
||||
<p className="text-foreground line-clamp-3 text-xs leading-snug">
|
||||
{stripChartTokens(annotation)}
|
||||
</p>
|
||||
{confidence ? (
|
||||
|
||||
@@ -110,22 +110,40 @@ export function TrendsRow({ annotations, confidence }: TrendsRowProps) {
|
||||
v1.4.27 MB7 / CF-71 — drop the unconditional `min-h-[300px]`
|
||||
floor to `md:min-h-[300px]` so mobile single-column doesn't
|
||||
eat dead space below a short chart; the floor stays on `md+`
|
||||
to preserve the equal-height baseline across the row. */}
|
||||
<div className="grid grid-cols-1 gap-4 md:auto-rows-fr md:grid-cols-3 md:items-stretch">
|
||||
to preserve the equal-height baseline across the row.
|
||||
|
||||
v1.4.28 R3c-Insights (FB-K1/K2) — three-slot template.
|
||||
`auto-rows-fr` covers every viewport, not just `md+`, so the
|
||||
mobile single-column path picks up the same row contract
|
||||
when the user expands the strip side-by-side via the
|
||||
orientation-change. The chart slot is now wrapped in a
|
||||
fixed-height shell so BP / weight / mood all paint the
|
||||
series at the same vertical position; the annotation slot
|
||||
clamps via `<TrendAnnotation>` so long captions can't pull
|
||||
the row taller than the design constant. */}
|
||||
<div className="grid auto-rows-fr grid-cols-1 gap-4 md:grid-cols-3 md:items-stretch">
|
||||
<div
|
||||
data-slot="trends-row-card"
|
||||
data-metric="bp"
|
||||
className="flex h-full flex-col gap-2 md:min-h-[300px]"
|
||||
>
|
||||
<HealthChart
|
||||
types={["BLOOD_PRESSURE_SYS", "BLOOD_PRESSURE_DIA"]}
|
||||
title={t("charts.bloodPressure")}
|
||||
colors={["#ff79c6", "#8be9fd"]}
|
||||
unit="mmHg"
|
||||
yAxisUnit="Hg"
|
||||
mini
|
||||
userTimezone={userTimezone}
|
||||
/>
|
||||
{/* v1.4.28 R3c-Insights — fixed chart slot. `<HealthChart>`
|
||||
mini paints its own 140 px chart band; this wrapper pins
|
||||
the total chart-envelope height so the mood tile's Card
|
||||
wrapper (which carries a heavier shell on a default
|
||||
shadcn Card) lines up with the BP/weight tiles' lighter
|
||||
shell. Both chart types ship the same data-slot now. */}
|
||||
<div data-slot="trends-row-chart-slot" className="shrink-0">
|
||||
<HealthChart
|
||||
types={["BLOOD_PRESSURE_SYS", "BLOOD_PRESSURE_DIA"]}
|
||||
title={t("charts.bloodPressure")}
|
||||
colors={["#ff79c6", "#8be9fd"]}
|
||||
unit="mmHg"
|
||||
yAxisUnit="Hg"
|
||||
mini
|
||||
userTimezone={userTimezone}
|
||||
/>
|
||||
</div>
|
||||
<TrendAnnotation
|
||||
metric="bp"
|
||||
annotation={bpAnnotation}
|
||||
@@ -137,14 +155,16 @@ export function TrendsRow({ annotations, confidence }: TrendsRowProps) {
|
||||
data-metric="weight"
|
||||
className="flex h-full flex-col gap-2 md:min-h-[300px]"
|
||||
>
|
||||
<HealthChart
|
||||
types={["WEIGHT"]}
|
||||
title={t("charts.weight")}
|
||||
colors={["#bd93f9"]}
|
||||
unit="kg"
|
||||
mini
|
||||
userTimezone={userTimezone}
|
||||
/>
|
||||
<div data-slot="trends-row-chart-slot" className="shrink-0">
|
||||
<HealthChart
|
||||
types={["WEIGHT"]}
|
||||
title={t("charts.weight")}
|
||||
colors={["#bd93f9"]}
|
||||
unit="kg"
|
||||
mini
|
||||
userTimezone={userTimezone}
|
||||
/>
|
||||
</div>
|
||||
<TrendAnnotation
|
||||
metric="weight"
|
||||
annotation={weightAnnotation}
|
||||
@@ -156,11 +176,13 @@ export function TrendsRow({ annotations, confidence }: TrendsRowProps) {
|
||||
data-metric="mood"
|
||||
className="flex h-full flex-col gap-2 md:min-h-[300px]"
|
||||
>
|
||||
<MoodChart
|
||||
title={t("charts.mood")}
|
||||
mini
|
||||
userTimezone={userTimezone}
|
||||
/>
|
||||
<div data-slot="trends-row-chart-slot" className="shrink-0">
|
||||
<MoodChart
|
||||
title={t("charts.mood")}
|
||||
mini
|
||||
userTimezone={userTimezone}
|
||||
/>
|
||||
</div>
|
||||
<TrendAnnotation
|
||||
metric="mood"
|
||||
annotation={moodAnnotation}
|
||||
|
||||
Reference in new issue
Block a user