diff --git a/src/components/insights/__tests__/trends-row.test.tsx b/src/components/insights/__tests__/trends-row.test.tsx index b69b00b9e..c8b18b288 100644 --- a/src/components/insights/__tests__/trends-row.test.tsx +++ b/src/components/insights/__tests__/trends-row.test.tsx @@ -106,7 +106,7 @@ describe("", () => { expect(slots.length).toBe(3); }); - it("pins every chart slot to h-[180px] so Recharts mounts with a known size (CLS fix)", () => { + it("pins every chart slot to a known responsive height so Recharts mounts with a size (CLS fix)", () => { // v1.4.36 W2 — without an explicit height the Recharts // ResponsiveContainer mounts with width=-1 height=-1 and emits a // console warning per chart. The mood card also drifted taller @@ -119,12 +119,17 @@ describe("", () => { // TrendAnnotation row below — visible as text sitting on top of // the chart. 180 px absorbs the full envelope so the three tiles // share one chart-band baseline without overlap. + // + // v1.22.0 — the slot height became `clamp(140px,32vh,180px)` so a + // rotated phone doesn't lose half the screen to the fixed band. The + // 180 px envelope still holds on desktop + portrait (where 32vh + // clears 180 px); only short landscape viewports shrink to the floor. const html = render(); const slots = html.match(/data-slot="trends-row-chart-slot"[^>]*class="[^"]*"/g) ?? []; expect(slots.length).toBe(3); for (const slot of slots) { - expect(slot).toMatch(/h-\[180px\]/); + expect(slot).toMatch(/h-\[clamp\(140px,32vh,180px\)\]/); } }); diff --git a/src/components/insights/mood/mood-distribution-chart.tsx b/src/components/insights/mood/mood-distribution-chart.tsx index 9e1f64f8b..cd11da3cb 100644 --- a/src/components/insights/mood/mood-distribution-chart.tsx +++ b/src/components/insights/mood/mood-distribution-chart.tsx @@ -56,7 +56,7 @@ export function MoodDistributionChart({ // v1.15.14 — bounded compact height (was `aspect-[3/2] min-h-[180px]`, // which ballooned the card on a wide viewport). A fixed band keeps this a // tidy card rather than a dominant block, and matches the weekday sibling. -
+
, + loading: () => ( + + ), }, ); const MoodWeekdayChart = dynamic( @@ -56,7 +58,9 @@ const MoodWeekdayChart = dynamic( })), { ssr: false, - loading: () => , + loading: () => ( + + ), }, ); const MoodTimeOfDayChart = dynamic( @@ -66,7 +70,9 @@ const MoodTimeOfDayChart = dynamic( })), { ssr: false, - loading: () => , + loading: () => ( + + ), }, ); import { MoodTagBreakdown, type MoodTagRow } from "./mood-tag-breakdown"; diff --git a/src/components/insights/mood/mood-time-of-day-chart.tsx b/src/components/insights/mood/mood-time-of-day-chart.tsx index 5a5aa102b..2a8769fc0 100644 --- a/src/components/insights/mood/mood-time-of-day-chart.tsx +++ b/src/components/insights/mood/mood-time-of-day-chart.tsx @@ -78,7 +78,7 @@ export function MoodTimeOfDayChart({ width-constrained), so an aspect ratio derived the height off the full card width and ballooned the chart to ~800px on a wide viewport. A bounded height keeps it the same size as the sibling charts. */} -
+
+
` and - * renders three blocks: + * renders two blocks: * - * 1. Headline card with the average nightly total (h / min) and the - * number of nights covered. - * 2. Stage composition stacked bar (REM / Deep / Core / Awake / In + * 1. Stage composition stacked bar (REM / Deep / Core / Awake / In * bed / Asleep) — the canonical sleep-phase distribution. - * 3. Duration trend chart with chart-cog parity (`chartKey="sleep"`). + * 2. Duration trend chart with chart-cog parity (`chartKey="sleep"`). + * + * v1.22.0 — the leading "average per night" headline card was dropped: + * the same figure + label already renders in `` inside + * the shared sleep-rhythm grid row directly below, so the top card only + * duplicated it. * * v1.18.7 W-D — the single-night "Last night" hypnogram card was removed: * its per-stage breakdown (time + %) duplicated the phase distribution the @@ -80,21 +83,9 @@ interface SleepAnalyticsResponse { sleepStages: SleepStageBreakdown | null; } -function formatHoursMinutes( - totalMinutes: number, - locale: string, -): { primary: string } { - const hours = Math.floor(totalMinutes / 60); - const mins = Math.round(totalMinutes - hours * 60); - if (locale === "de") { - return { primary: `${hours} Std. ${mins} Min.` }; - } - return { primary: `${hours}h ${mins}m` }; -} - export function SleepOverview() { const { isAuthenticated, user } = useAuth(); - const { t, locale } = useTranslations(); + const { t } = useTranslations(); const { compareBaseline } = useInsightsLayoutPrefs(isAuthenticated); // v1.4.33 IW2 — sleep overview reads `sleepStages` (a thick-only @@ -127,43 +118,8 @@ export function SleepOverview() { ); } - const averageMinutes = sleepSummary?.avg30 ?? sleepSummary?.avg7 ?? null; - const suffix = t("insights.sleep.headlineCaptionSuffix"); - const { primary } = - averageMinutes != null - ? formatHoursMinutes(averageMinutes, locale) - : { primary: "—" }; - return (
- {/* v1.12.0 — headline card tightened: the default Card - `gap-4 md:gap-6` + `py-4 md:py-6` left a tall empty band around a - two-line readout. `gap-2 py-4 md:py-4` pulls the caption up under the - number without crowding it. */} - - -
- - - {t("insights.sleep.headlineTitle")} - -
-
- -
-

{primary}

-

- {sleepSummary?.count - ? t("insights.sleep.headlineCaption", { - count: sleepSummary.count, - suffix, - }) - : suffix} -

-
-
-
- {sleepStages ? ( ) : ( diff --git a/src/components/insights/trends-row.tsx b/src/components/insights/trends-row.tsx index 4056c052e..26a48a6bc 100644 --- a/src/components/insights/trends-row.tsx +++ b/src/components/insights/trends-row.tsx @@ -277,7 +277,14 @@ export function TrendsRow({ lives entirely on this slot. */}