insights: lead every metric subpage with the stat strip above the chart

Render the Min/Max/Median/Mittelwert stat strip directly above the chart
on the canonical spine (intro -> stats -> chart -> target -> assessment),
the numbers-first detail-screen convention. Wire the series headings on
each subpage: blood pressure shows the full four-stat grid for both
systolic and diastolic as labelled blocks, single-series pages pass the
metric name and icon. Leave a marker for chart-brush-reactive stats as a
follow-up.
This commit is contained in:
Marc-André Bombeck committed 2026-06-05 12:20:54 +02:00
1 parent d434a6e24b
commit ea2ce8697e
5 files changed
+49 -20

No files matched your search

@@ -5,10 +5,13 @@ import { join } from "node:path";
/**
* v1.12.4 — canonical metric-detail spine guard.
*
* The unified subpage spine is: intro → chart → target card → assessment →
* stat strip. The assessment is the LAST content block the page hands to
* `<SubPageShell>` as `children`; the shell appends the Min/Max/Median/Mean
* stat strip below it. The generic `HealthKitMetricPage` scaffold enforces
* v1.12.6 — the unified subpage spine is: intro → stat strip → chart →
* target card → assessment. The stat strip moved ABOVE the chart (the
* shell renders the `statStrip` prop before `children`); the assessment
* stays the LAST content block the page hands to `<SubPageShell>` as
* `children`. This source-order guard is unaffected by the render-position
* move because `statStrip` is a prop on the opening tag and the assessment
* is still the final child element. The generic `HealthKitMetricPage` scaffold enforces
* this for the ~29 HealthKit pages structurally; the bespoke pages each
* hand-write their body, so a future edit can silently slot a card after the
* assessment (as `mood` and `medications` did before).
+3 -1
View File
@@ -1,7 +1,7 @@
"use client";
import Link from "next/link";
import { HeartPulse } from "lucide-react";
import { ArrowDownRight, ArrowUpRight, HeartPulse } from "lucide-react";
import { useAuth } from "@/hooks/use-auth";
import { useInsightsAnalytics } from "@/hooks/use-insights-analytics";
@@ -112,12 +112,14 @@ export default function InsightsBlutdruckPage() {
unit="mmHg"
fractionDigits={0}
seriesLabel={t("charts.systolic")}
icon={ArrowUpRight}
/>
<MetricStatStrip
summary={diaSummary}
unit="mmHg"
fractionDigits={0}
seriesLabel={t("charts.diastolic")}
icon={ArrowDownRight}
/>
</div>
}
+8 -1
View File
@@ -113,7 +113,14 @@ export default function InsightsPulsPage() {
title={t("insights.pulseSectionTitle")}
description={t("insights.subPage.pulsDescription")}
explainerMetric="pulse"
statStrip={<MetricStatStrip summary={pulseSummary} unit="bpm" />}
statStrip={
<MetricStatStrip
summary={pulseSummary}
unit="bpm"
seriesLabel={t("insights.pulseSectionTitle")}
icon={Heart}
/>
}
diversityNudge={
<MeasurementDiversityNudge
measurementType="PULSE"
+8 -1
View File
@@ -75,7 +75,14 @@ export default function InsightsGewichtPage() {
title={t("insights.weightSectionTitle")}
description={t("insights.subPage.gewichtDescription")}
explainerMetric="weight"
statStrip={<MetricStatStrip summary={weightSummary} unit="kg" />}
statStrip={
<MetricStatStrip
summary={weightSummary}
unit="kg"
seriesLabel={t("insights.weightSectionTitle")}
icon={Scale}
/>
}
diversityNudge={
<MeasurementDiversityNudge
measurementType="WEIGHT"
+23 -13
View File
@@ -80,10 +80,11 @@ export interface SubPageShellProps {
*/
focusOnMount?: boolean;
/**
* v1.12.4 — the Min / Max / Median / Mittelwert stat strip. It now sits
* at the FOOT of the canonical spine, below the assessment, so every
* metric subpage reads the same: intro → chart → target → assessment →
* stat strip. Sub-pages pass `<MetricStatStrip metric=… />`; the strip
* v1.12.6 — the Min / Max / Median / Mittelwert stat strip. It now leads
* the canonical spine, directly ABOVE the chart, so every metric subpage
* reads the same: intro → stat strip → chart → target → assessment.
* Numbers-first matches how Apple Health / Withings / Oura lead a detail
* screen. Sub-pages pass `<MetricStatStrip metric=… />`; the strip
* self-gates (renders nothing without data), so the empty-state and
* brand-new-metric paths stay clean. Omitted on the empty-state branch.
*/
@@ -255,17 +256,26 @@ export function SubPageShell({
<p className="text-muted-foreground text-sm">{description}</p>
) : null}
</header>
{/* v1.12.4 — the canonical spine body: intro (header, above) →
chart → target card → assessment, all rendered by the page as
`children`. */}
{children}
{/* v1.12.4 — the Min / Max / Median / Mittelwert stat strip closes
the spine, below the assessment. Self-gating (renders nothing
{/* v1.12.6 — the Min / Max / Median / Mittelwert stat strip LEADS
the spine, directly above the chart. Self-gating (renders nothing
without data), so the spacing rhythm holds on the empty-state and
brand-new-metric paths. The diversity nudge rides the heading row;
the headline tile and the "Letzte Messung" card are retired so the
subpage is identical by construction across every metric. */}
brand-new-metric paths. Numbers-first is the Apple-Health /
Withings / Oura detail-screen convention.
TODO(v1.12.7): chart-brush-reactive stats. When the user brushes /
selects a window in the chart below, recompute the stat strip's
Min/Max/Median/Mean for the visible domain. Deferred: the shared
`<HealthChart>` (~1900 LOC, also driving the dashboard) has no
Recharts `<Brush>` or lifted domain-change callback today, and its
range state lives internally; wiring a stable brush + lifting the
selected domain into this shell so both the chart and the strip read
it is a larger change than this release's scope warrants without
destabilising the chart. */}
{statStrip}
{/* v1.12.6 — the canonical spine body: intro (header) → stat strip
(above) → chart → target card → assessment. The page renders
chart → target → assessment as `children`. */}
{children}
{/* v1.8.5 W4b — "show all readings" entry at the foot, linking to
the dedicated per-metric values subpage.
v1.8.6 — normalised to the `h-10` secondary-button height so it