From 29e9f958f773d2594165bbc569137ad99e2d44ed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andr=C3=A9=20Bombeck?= Date: Sat, 16 May 2026 02:43:57 +0200 Subject: [PATCH] refactor(charts): finish HealthChartDynamic migration on trends row Two consumers (trends-row.tsx + vo2-max-chart-row.tsx) still hand-rolled the same dynamic() block plus a degenerate local ChartSkeleton shadow that bypassed the canonical primitive. Both files now import HealthChartDynamic; the local ChartSkeleton drops from trends-row and vo2-max-chart-row in favour of the shared chart-skeleton module. --- .../charts/health-chart-dynamic.tsx | 10 ++--- src/components/insights/trends-row.tsx | 42 ++++++------------- src/components/insights/vo2-max-chart-row.tsx | 22 +++------- 3 files changed, 24 insertions(+), 50 deletions(-) diff --git a/src/components/charts/health-chart-dynamic.tsx b/src/components/charts/health-chart-dynamic.tsx index a7224757a..da1babbf8 100644 --- a/src/components/charts/health-chart-dynamic.tsx +++ b/src/components/charts/health-chart-dynamic.tsx @@ -5,15 +5,15 @@ import dynamic from "next/dynamic"; import { ChartSkeleton } from "@/components/charts/chart-skeleton"; /** - * v1.4.28 R3d (BK-F-M2) — single `next/dynamic` boundary for the + * v1.4.28 (BK-F-M2) — single `next/dynamic` boundary for the * canonical `` component. * - * Pre-fix, six call sites (`src/app/page.tsx`, the five insights - * sub-pages, and `sleep-duration-chart.tsx`) duplicated the same + * Pre-fix, eight call sites (`src/app/page.tsx`, five insights + * sub-pages, `sleep-duration-chart.tsx`, `trends-row.tsx`, and + * `vo2-max-chart-row.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). + * flag had to be re-wired on every caller. * * Re-exporting through this module gives every consumer the same * pre-configured ``-backed lazy boundary. Consumers diff --git a/src/components/insights/trends-row.tsx b/src/components/insights/trends-row.tsx index bb34ec186..f383744c5 100644 --- a/src/components/insights/trends-row.tsx +++ b/src/components/insights/trends-row.tsx @@ -3,52 +3,36 @@ import dynamic from "next/dynamic"; import { useTranslations } from "@/lib/i18n/context"; import { useAuth } from "@/hooks/use-auth"; +import { ChartSkeleton } from "@/components/charts/chart-skeleton"; +import { HealthChartDynamic } from "@/components/charts/health-chart-dynamic"; import { TrendAnnotation, type TrendAnnotationConfidenceBand, } from "./trend-annotation"; /** - * v1.4.20 phase B3 — Trends row. - * - * Renders three small charts (BP / Weight / Mood) above a one-sentence - * AI annotation each. Recharts is ~108 KiB Brotli, so we mirror the - * `` defer-load pattern from `/insights` for - * the two HealthChart-backed cards. The MoodChart already has its own - * fetch wired so it composes naturally. + * Trends row — three small charts (BP / Weight / Mood) above a + * one-sentence assessment each. Recharts is ~108 KiB Brotli, so the + * three charts ride the shared lazy-import boundary + * (`` for BP + weight; MoodChart still resolves + * locally because no shared re-export exists yet for it). * * Layout: * - `=md`: 3-up grid, equal column tracks * - * Annotations come from `trendAnnotations.{bp,weight,mood}` on the AI - * advisor payload. When a metric's annotation is null, the + * Annotations come from `trendAnnotations.{bp,weight,mood}` on the + * Insights payload. When a metric's annotation is null, the * `` empty state hints at the gap without breaking * the row's visual rhythm. */ -// v1.4.22 W5 reconcile (S-04) — both `dynamic()` calls used the same -// pulse-skeleton placeholder. Hoist so the loading affordance stays -// in lock-step (cleared against `feedback_charts_visual_identity.md` -// — placeholder only, no chart visual change). -const ChartSkeleton = () => ( -
-); - -const HealthChart = dynamic( - () => - import("@/components/charts/health-chart").then((mod) => ({ - default: mod.HealthChart, - })), - { ssr: false, loading: ChartSkeleton }, -); - const MoodChart = dynamic( () => import("@/components/charts/mood-chart").then((mod) => ({ default: mod.MoodChart, })), - { ssr: false, loading: ChartSkeleton }, + { ssr: false, loading: () => }, ); interface TrendsRowProps { @@ -127,14 +111,14 @@ export function TrendsRow({ annotations, confidence }: TrendsRowProps) { data-metric="bp" className="flex h-full flex-col gap-2 md:min-h-[300px]" > - {/* v1.4.28 R3c-Insights — fixed chart slot. `` + {/* v1.4.28 R3c-Insights — fixed chart slot. `` 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. */}
-
- ` + * boundary so the lazy-import + skeleton-loading state stays in + * lock-step with the other Insights chart consumers. */ -// Mirror the Recharts defer-load + skeleton used by `` so the -// VO2 chart-row doesn't blow up the Insights bundle. -const ChartSkeleton = () => ( -
-); - -const HealthChart = dynamic( - () => - import("@/components/charts/health-chart").then((mod) => ({ - default: mod.HealthChart, - })), - { ssr: false, loading: ChartSkeleton }, -); - interface Vo2MaxChartRowProps { /** * Analytics summary for `MeasurementType=VO2_MAX`. Null is allowed so @@ -205,7 +195,7 @@ export function Vo2MaxChartRow({ the dashboard VO2 tile and from the pulse chart sitting above it on the same page. */} {hasData ? ( -