From 8f3bfc37a0f964b0a2987ad585aeb5a389b17915 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andr=C3=A9=20Bombeck?= Date: Sat, 16 May 2026 01:52:18 +0200 Subject: [PATCH] refactor(charts): collapse health-chart dynamic imports onto re-export --- src/app/insights/blutdruck/page.tsx | 13 ++------- src/app/insights/bmi/page.tsx | 15 ++-------- src/app/insights/gewicht/page.tsx | 13 ++------- src/app/insights/puls/page.tsx | 13 ++------- src/app/page.tsx | 22 +++++--------- .../charts/health-chart-dynamic.tsx | 29 +++++++++++++++++++ .../insights/sleep-duration-chart.tsx | 22 +++++--------- 7 files changed, 54 insertions(+), 73 deletions(-) create mode 100644 src/components/charts/health-chart-dynamic.tsx diff --git a/src/app/insights/blutdruck/page.tsx b/src/app/insights/blutdruck/page.tsx index 4bfc3ed77..4d93f61d0 100644 --- a/src/app/insights/blutdruck/page.tsx +++ b/src/app/insights/blutdruck/page.tsx @@ -1,7 +1,6 @@ "use client"; import { useQuery } from "@tanstack/react-query"; -import dynamic from "next/dynamic"; import Link from "next/link"; import { HeartPulse } from "lucide-react"; @@ -11,7 +10,7 @@ import { useTranslations } from "@/lib/i18n/context"; import { useInsightsLayoutPrefs } from "@/hooks/use-insights-layout-prefs"; import { Button } from "@/components/ui/button"; import { EmptyState } from "@/components/ui/empty-state"; -import { ChartSkeleton } from "@/components/charts/chart-skeleton"; +import { HealthChartDynamic } from "@/components/charts/health-chart-dynamic"; import { CoachLaunchButton } from "@/components/insights/coach-launch-button"; import { InsightStatusCard } from "@/components/insights/insight-status-card"; import { SubPageShell } from "@/components/insights/sub-page-shell"; @@ -38,14 +37,6 @@ import { hasMetricData } from "@/lib/insights/metric-availability"; interface AnalyticsData { summaries: Record; } -const HealthChart = dynamic( - () => - import("@/components/charts/health-chart").then((mod) => ({ - default: mod.HealthChart, - })), - { ssr: false, loading: () => }, -); - export default function InsightsBlutdruckPage() { const { isAuthenticated, user } = useAuth(); const { t } = useTranslations(); @@ -127,7 +118,7 @@ export default function InsightsBlutdruckPage() { title={t("insights.bloodPressureSectionTitle")} description={t("insights.subPage.blutdruckDescription")} > - ` and the + * so the chart sets `valueMode="bmi"` on `` and the * underlying WEIGHT series is reused. When the user has no height set * the chart can't compute; we surface the same plain empty-state the * mother page used. */ -const HealthChart = dynamic( - () => - import("@/components/charts/health-chart").then((mod) => ({ - default: mod.HealthChart, - })), - { ssr: false, loading: () => }, -); - const BMI_BANDS = [ { min: 0, max: 17, color: "#ff5555", opacity: 0.16 }, { min: 17, max: 18.5, color: "#ffb86c", opacity: 0.18 }, @@ -131,7 +122,7 @@ export default function InsightsBmiPage() { title={t("insights.bmiSectionTitle")} description={t("insights.subPage.bmiDescription")} > - - import("@/components/charts/health-chart").then((mod) => ({ - default: mod.HealthChart, - })), - { ssr: false, loading: () => }, -); - export default function InsightsGewichtPage() { const { isAuthenticated, user } = useAuth(); const { t } = useTranslations(); @@ -103,7 +94,7 @@ export default function InsightsGewichtPage() { title={t("insights.weightSectionTitle")} description={t("insights.subPage.gewichtDescription")} > - - import("@/components/charts/health-chart").then((mod) => ({ - default: mod.HealthChart, - })), - { ssr: false, loading: () => }, -); - interface AnalyticsData { summaries: Record; } @@ -140,7 +131,7 @@ export default function InsightsPulsPage() { title={t("insights.pulseSectionTitle")} description={t("insights.subPage.pulsDescription")} > - - import("@/components/charts/health-chart").then((mod) => ({ - default: mod.HealthChart, - })), - { ssr: false, loading: () => }, -); const MoodChart = dynamic( () => import("@/components/charts/mood-chart").then((mod) => ({ @@ -959,7 +953,7 @@ export default function DashboardPage() { order: widgetOrder("weight"), count: w?.count ?? 0, node: ( - ` component. + * + * Pre-fix, six call sites (`src/app/page.tsx`, the five insights + * sub-pages, and `sleep-duration-chart.tsx`) duplicated the same + * `dynamic(() => import("@/components/charts/health-chart"), { ... })` + * incantation. The duplication meant the loading skeleton + `ssr: false` + * flag had to be re-wired on every caller (R1.2 H4 paid for this once; + * this commit prevents future regressions). + * + * Re-exporting through this module gives every consumer the same + * pre-configured ``-backed lazy boundary. Consumers + * still forward the same props they did before — the type contract + * mirrors `` exactly via `dynamic()`'s own inference. + */ +export const HealthChartDynamic = dynamic( + () => + import("@/components/charts/health-chart").then((mod) => ({ + default: mod.HealthChart, + })), + { ssr: false, loading: () => }, +); diff --git a/src/components/insights/sleep-duration-chart.tsx b/src/components/insights/sleep-duration-chart.tsx index 5e5e31095..232543d5b 100644 --- a/src/components/insights/sleep-duration-chart.tsx +++ b/src/components/insights/sleep-duration-chart.tsx @@ -1,16 +1,14 @@ "use client"; -import dynamic from "next/dynamic"; - -import { ChartSkeleton } from "@/components/charts/chart-skeleton"; +import { HealthChartDynamic } from "@/components/charts/health-chart-dynamic"; import { useTranslations } from "@/lib/i18n/context"; import type { ComparisonBaseline } from "@/lib/dashboard-layout"; /** * v1.4.25 W4c — total nightly sleep duration over time. * - * Thin wrapper around `` so the sleep sub-page gets the - * same chart-cog parity (`chartKey="sleep"`) as the dashboard chart + * Thin wrapper around `` so the sleep sub-page gets + * the same chart-cog parity (`chartKey="sleep"`) as the dashboard chart * surfaces. SLEEP_DURATION rows from Apple Health are per-stage (one * row per stage per night) — the analytics API aggregates per Berlin * day before summarising, and `HealthChart` itself relies on the @@ -21,15 +19,11 @@ import type { ComparisonBaseline } from "@/lib/dashboard-layout"; * (v1.4.23 schema note). The chart shows the raw minutes value with a * "min" unit suffix — the parent sub-page renders a separate * "X h Y min" headline above the chart for the human-friendly read. + * + * v1.4.28 R3d (BK-F-M2) — the inline `dynamic()` call site was retired + * in favour of the shared `` re-export so the + * `` + `ssr: false` configuration stays in one place. */ -const HealthChart = dynamic( - () => - import("@/components/charts/health-chart").then((mod) => ({ - default: mod.HealthChart, - })), - { ssr: false, loading: () => }, -); - export interface SleepDurationChartProps { compareBaseline?: ComparisonBaseline; userTimezone?: string; @@ -41,7 +35,7 @@ export function SleepDurationChart({ }: SleepDurationChartProps) { const { t } = useTranslations(); return ( -