From fa161f7f12d86d1c5f6892d4eb77436ef6696940 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andr=C3=A9=20Bombeck?= Date: Sat, 27 Jun 2026 17:56:42 +0200 Subject: [PATCH 1/2] fix(insights): drop the duplicate average-per-night header in sleep The Sleep view led with an average-per-night headline card that repeated the same figure and label already shown in the average-sleep tile within the sleep-rhythm grid row below it. Remove the leading card and let the tile carry the readout; the stage-distribution bar and duration trend chart stay in place. --- src/components/insights/sleep-overview.tsx | 64 ++++------------------ 1 file changed, 10 insertions(+), 54 deletions(-) diff --git a/src/components/insights/sleep-overview.tsx b/src/components/insights/sleep-overview.tsx index fa561ca97..aaf123bcf 100644 --- a/src/components/insights/sleep-overview.tsx +++ b/src/components/insights/sleep-overview.tsx @@ -9,7 +9,7 @@ import { useTranslations } from "@/lib/i18n/context"; import { useInsightsLayoutPrefs } from "@/hooks/use-insights-layout-prefs"; import { useAnalyticsQuery } from "@/lib/queries/use-analytics-query"; import { Button } from "@/components/ui/button"; -import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; +import { Card, CardContent } from "@/components/ui/card"; import { EmptyState } from "@/components/ui/empty-state"; import dynamic from "next/dynamic"; import { ChartErrorBoundary } from "@/components/charts/chart-error-state"; @@ -45,13 +45,16 @@ function SleepStageStackedBar( * * Pulls the analytics `sleepStages` aggregate (added in v1.4.23 W2) * plus the SLEEP_DURATION trend series via `` 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 ? ( ) : ( From ba497f4ae8c614a50dd8807d52d945fc4733f39b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andr=C3=A9=20Bombeck?= Date: Sat, 27 Jun 2026 17:56:55 +0200 Subject: [PATCH 2/2] fix(insights): keep charts from crushing the layout in landscape Fixed-pixel chart bands ate roughly half the screen when a small phone was rotated to landscape. Swap the brittle heights for viewport-relative clamps so the chart envelope holds its previous size on desktop and portrait but shrinks toward a floor on short landscape viewports. Covers the overview trends-row slot and the mood distribution, weekday, and time-of-day charts (with matching skeleton heights). Chart series and styling are untouched; only the container heights flex. --- .../insights/__tests__/trends-row.test.tsx | 9 +++++++-- .../insights/mood/mood-distribution-chart.tsx | 2 +- .../insights/mood/mood-insights-sections.tsx | 12 +++++++++--- .../insights/mood/mood-time-of-day-chart.tsx | 2 +- src/components/insights/mood/mood-weekday-chart.tsx | 2 +- src/components/insights/trends-row.tsx | 9 ++++++++- 6 files changed, 27 insertions(+), 9 deletions(-) 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. */} -
+
+