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.
This commit is contained in:
Marc-André Bombeck committed 2026-05-16 02:43:57 +02:00
1 parent 9768066354
commit 29e9f958f7
3 files changed
+24 -50

No files matched your search

@@ -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 `<HealthChart>` 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 `<ChartSkeleton>`-backed lazy boundary. Consumers
+13 -29
View File
@@ -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
* `<ScatterCorrelationChart>` 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
* (`<HealthChartDynamicDynamic>` for BP + weight; MoodChart still resolves
* locally because no shared re-export exists yet for it).
*
* Layout:
* - `<md`: single column, full width
* - `>=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
* `<TrendAnnotation>` 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 = () => (
<div className="bg-muted/40 h-[220px] w-full animate-pulse rounded-md motion-reduce:animate-none" />
);
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: () => <ChartSkeleton /> },
);
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. `<HealthChart>`
{/* v1.4.28 R3c-Insights — fixed chart slot. `<HealthChartDynamic>`
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
<HealthChartDynamic
types={["BLOOD_PRESSURE_SYS", "BLOOD_PRESSURE_DIA"]}
title={t("charts.bloodPressure")}
colors={["#ff79c6", "#8be9fd"]}
@@ -156,7 +140,7 @@ export function TrendsRow({ annotations, confidence }: TrendsRowProps) {
className="flex h-full flex-col gap-2 md:min-h-[300px]"
>
<div data-slot="trends-row-chart-slot" className="shrink-0">
<HealthChart
<HealthChartDynamic
types={["WEIGHT"]}
title={t("charts.weight")}
colors={["#bd93f9"]}
+6 -16
View File
@@ -1,12 +1,12 @@
"use client";
import dynamic from "next/dynamic";
import { ArrowDown, ArrowRight, ArrowUp, Gauge, Minus } from "lucide-react";
import { useTranslations, useFormatters } from "@/lib/i18n/context";
import type { ComparisonBaseline } from "@/lib/dashboard-layout";
import type { DataSummary } from "@/lib/analytics/trends";
import { cn } from "@/lib/utils";
import { HealthChartDynamic } from "@/components/charts/health-chart-dynamic";
/**
* v1.4.25 W16a — VO2 max chart-row card for `/insights/puls`.
@@ -22,22 +22,12 @@ import { cn } from "@/lib/utils";
* The card stays mounted even when `summary` reports zero samples — a
* brand-new account gets the "no data yet" hint rather than a missing
* surface, matching the dashboard tile's opt-in pattern.
*
* The HealthChart mounts behind the shared `<HealthChartDynamic>`
* 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 `<TrendsRow>` so the
// VO2 chart-row doesn't blow up the Insights bundle.
const ChartSkeleton = () => (
<div className="bg-muted/40 h-[220px] w-full animate-pulse rounded-md motion-reduce:animate-none" />
);
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 ? (
<HealthChart
<HealthChartDynamic
chartKey="vo2Max"
types={["VO2_MAX"]}
title={title}