From fba8553192cfb353105131260c7320e5504d8981 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andr=C3=A9=20Bombeck?= Date: Sun, 28 Jun 2026 15:40:47 +0200 Subject: [PATCH] fix(insights): remove the trajectory continuation tile from metric pages The short-horizon trajectory tile read as a wall of text with no clear value on the metric pages. Drop it from the weight page and the shared HealthKit metric page (the opt-in forecast slot is removed entirely, not left dead), and delete the now-orphaned tile + fan components. The trajectory engine stays for the Coach narrative; only the metric-page surfacing is removed. --- src/app/insights/weight/page.tsx | 9 - .../insights/derived/trajectory-fan.tsx | 79 ----- .../derived/trajectory-forecast-card.tsx | 331 ------------------ .../insights/healthkit-metric-page.tsx | 24 -- 4 files changed, 443 deletions(-) delete mode 100644 src/components/insights/derived/trajectory-fan.tsx delete mode 100644 src/components/insights/derived/trajectory-forecast-card.tsx diff --git a/src/app/insights/weight/page.tsx b/src/app/insights/weight/page.tsx index b621a2600..453a2e3e4 100644 --- a/src/app/insights/weight/page.tsx +++ b/src/app/insights/weight/page.tsx @@ -18,7 +18,6 @@ import { MetricCorrelationCard } from "@/components/insights/metric-correlation- import { MeasurementDiversityNudge } from "@/components/insights/measurement-diversity-nudge"; import { MetricTargetSummary } from "@/components/insights/metric-target-summary"; import { SubPageShell } from "@/components/insights/sub-page-shell"; -import { TrajectoryForecastCard } from "@/components/insights/derived/trajectory-forecast-card"; import { buildWeightBandsFromHeight } from "@/lib/analytics/value-bands"; /** @@ -115,14 +114,6 @@ export default function InsightsGewichtPage() { - - {/* v1.12.0 — Weight owns the weight × weekday correlation (relocated off the overview onto its metric page). */} diff --git a/src/components/insights/derived/trajectory-fan.tsx b/src/components/insights/derived/trajectory-fan.tsx deleted file mode 100644 index d3f8b11ad..000000000 --- a/src/components/insights/derived/trajectory-fan.tsx +++ /dev/null @@ -1,79 +0,0 @@ -"use client"; - -import { - ResponsiveContainer, - ComposedChart, - Area, - Line, - XAxis, - YAxis, - ReferenceLine, -} from "recharts"; - -/** - * v1.12.1 — the Recharts fan band of the trajectory forecast card, - * split into its own module so it can be `next/dynamic`-deferred. - * - * `TrajectoryForecastCard` is mounted by the shared `HealthKitMetricPage` - * on every trajectory-eligible metric page; before this split it pulled - * Recharts into the page's initial chunk eagerly (the main metric chart - * is already deferred via `HealthChartDynamic`). Isolating the only - * Recharts-touching subtree here lets the card chrome render without the - * chart library and lazy-load this chunk only when there's a projection - * to draw — keeping the chart visually identical (same ComposedChart / - * Area / Line / ReferenceLine geometry, same `isAnimationActive={false}`). - */ - -export interface TrajectoryFanPoint { - date: string; - projected: number; - /** Stacked transparent base (= bandLow). */ - base: number; - /** Shaded band height (= bandHigh − bandLow). */ - range: number; -} - -export function TrajectoryFan({ - points, - color, -}: { - points: TrajectoryFanPoint[]; - color: string; -}) { - return ( - - - - - - - - - - - ); -} diff --git a/src/components/insights/derived/trajectory-forecast-card.tsx b/src/components/insights/derived/trajectory-forecast-card.tsx deleted file mode 100644 index d9cc97903..000000000 --- a/src/components/insights/derived/trajectory-forecast-card.tsx +++ /dev/null @@ -1,331 +0,0 @@ -"use client"; - -import { useMemo } from "react"; -import dynamic from "next/dynamic"; -import { TrendingUp, TrendingDown, Minus } from "lucide-react"; - -import { useTranslations } from "@/lib/i18n/context"; -import { cn } from "@/lib/utils"; -import { LearningGate } from "@/components/ui/learning-gate"; -import { CoverageMeter } from "@/components/insights/derived/coverage-meter"; -import { ProvenanceExplainer } from "@/components/insights/derived/provenance-explainer"; -import { METRIC_PROVENANCE } from "@/components/insights/derived/standards"; -import { useDerivedMetric } from "@/components/insights/derived/use-derived-metric"; - -// v1.12.1 — defer the Recharts fan band so the card chrome (and the whole -// HealthKit metric page that always mounts this card on a trajectory- -// eligible type) no longer pulls Recharts into the initial chunk. The -// loading shell is a plain box that fills the same fixed-height band the -// chart paints, so there is no layout shift when the chunk arrives. -const TrajectoryFanDynamic = dynamic( - () => - import("@/components/insights/derived/trajectory-fan").then((mod) => ({ - default: mod.TrajectoryFan, - })), - { - ssr: false, - loading: () =>
, - }, -); -// Type-only — the compute payload never drags the server graph into the -// client bundle (the v1.9.0 lesson, mirrored across the derived surfaces). -import type { TrajectoryValue } from "@/lib/insights/derived/trajectory"; -import type { DerivedProvenance } from "@/lib/insights/derived/types"; - -/** - * v1.11.0 (Epic B, Pillar 3) — the short-horizon projection card on the - * metric detail page. - * - * Surfaces the deterministic `TRAJECTORY` derived metric as a small band - * chart: the recent observed line, then the projected line (dashed) with a - * widening prediction-interval fan. The fan VISIBLY widens with the horizon - * — that is the uncertainty communication, no words needed. The number is - * computed server-side; this card only draws it. It is explicitly labelled - * a PROJECTION ("if this pattern continues"), never a prediction or a dated - * event, and the cited method (OLS + prediction interval) rides the ⓘ - * provenance affordance. - * - * Below the engine's R² / history / staleness floor the engine returns - * `insufficient`, and the card renders the same calm "not enough of a trend - * to project yet" + coverage state every derived surface uses — never a weak - * line, never a blank. - * - * The card mounts as a sibling beneath the main metric chart (mirroring - * `MetricStatusCard`), so the main chart's visual identity is untouched. - */ - -const TRAJECTORY_METRIC = "TRAJECTORY"; - -/** - * Mirror of `TRAJECTORY_MIN_HISTORY_DAYS` (`src/lib/insights/derived/trajectory.ts`). - * Re-declared as a client-side literal rather than value-imported because the - * engine module pulls the rollup/baseline server graph in at its top level — - * importing the constant would drag that graph into the client bundle (the same - * reason `TrajectoryValue` is a type-only import). Keep this in lockstep with - * the engine; the unit test pins both at 14. - */ -const TRAJECTORY_MIN_HISTORY_DAYS = 14; - -/** A single value-scale used to render stored values in the display unit. */ -function scaled(value: number, scale: number): number { - return value * scale; -} - -interface TrajectoryForecastCardProps { - /** The MeasurementType to project (the page's canonical type). */ - type: string; - /** Display unit suffix (e.g. "kg"). */ - unit?: string; - /** Display scale folded into every rendered value (defaults to 1). */ - valueScale?: number; - /** Canonical chart colour for the metric's line. */ - color?: string; - /** Gate the underlying derived read (e.g. on the auth/empty flag). */ - enabled?: boolean; - /** - * v1.12.0 — density by render context. Sub-pages pass `compact` so the - * card sheds the `min-h-48` floor + drops to a shorter chart and a tighter - * no-trend state (a too-tall "1 of 1" empty band was the maintainer flag). - * The Insights OVERVIEW grid omits it so the tile KEEPS its current size - * for grid symmetry — the default is the unchanged, roomier geometry. - */ - compact?: boolean; - className?: string; -} - -/** The provenance ⓘ explainer for the projection, wired from the map. */ -function TrajectoryProvenance({ - provenance, -}: { - provenance: DerivedProvenance; -}) { - const { t } = useTranslations(); - const meta = METRIC_PROVENANCE.TRAJECTORY; - const method = ( - <> - {meta.caveatKey ? ( - - {t(meta.caveatKey)} - - ) : null} - {t(meta.methodKey)} - - ); - return ( - - ); -} - -/** Card shell — uppercase label + provenance affordance + body children. */ -function CardShell({ - provenance, - compact = false, - children, -}: { - provenance?: DerivedProvenance; - compact?: boolean; - children: React.ReactNode; -}) { - const { t } = useTranslations(); - return ( -
-
- - {t("insights.derived.trajectory.cardTitle")} - - {provenance ? : null} -
- {children} -
- ); -} - -/** CLS-safe skeleton matching the resolved card geometry. */ -function CardSkeleton({ compact = false }: { compact?: boolean }) { - return ( -