Commit Graph
7 Commits
Author SHA1 Message Date
Marc-André Bombeck ea2ce8697e 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.
2026-06-05 12:20:54 +02:00
Marc-André Bombeck 4ace1b7706 refactor(insights): unify the metric subpage template — target tile, order, stat strip, drop the range row
Make every metric subpage identical by construction in the shared
scaffold rather than per-page bespoke.

- Target tile matches the other cards: dark `bg-card` background and the
  `rounded-xl border p-4` spacing the assessment and stat-strip cards
  use, instead of the old translucent `bg-card/20` panel.
- Drop the 7T/30T/90T/1J range row and its "+X% vs previous" delta from
  every subpage. The chart owns its own window via its in-card range
  tabs, so the chart query is unaffected. Retire the now-dead
  `MetricRangeControls`, `MetricRangeDelta`, `TimeRangePills`,
  `useAnalyticsRange`, the `useInsightsRangePref` persisted-range hook,
  and `sentimentFromMetricDirection` along with their tests.
- Fix the spine order to intro -> chart -> target card -> assessment ->
  Min/Max/Median/Mittelwert stat strip. The stat strip moves to the foot
  of the spine, below the assessment. Remove the "Letzte Messung" card
  and the headline primary tile from subpages; retire
  `MetricLastMeasurementCard` and `MetricPrimaryTile`.
- Blood pressure gains the stat strip it was missing: two stacked
  systolic / diastolic strips, since a single-series strip would drop the
  diastolic half. `MetricStatStrip` takes an optional series caption.

Update the spine guard to assert the target card precedes the
assessment and that the retired range row and last-measurement card
appear on no subpage.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
(cherry picked from commit 80b2a810314b4c0951c0d68c04bffc89003005d6)
2026-06-05 10:42:47 +02:00
Marc-André Bombeck 496147687d refactor(insights): unify the assessment term, restore the canonical spine, consolidate status cards
The per-metric AI assessment was named four ways across the bundle
(Einschätzung / Auswertung / Analyse / Bewertung in German, with parallel
drift in the other locales). Sweep every assessment-state value — the
no-provider, no-result, start / refresh / re-run, regenerate and warm
strings — onto the one noun the card title already carries, in all six
locales. Keys are unchanged; only the human strings unify.

Restore the canonical metric-detail spine on the two bespoke pages that had
drifted: the mood page rendered the assessment directly under the first
chart with the target summary and breakdown sections below it, and the
medications page rendered the therapy timeline after the assessment. Both
now end on the assessment block, matching weight / bmi / pulse /
blood-pressure and the generic scaffold. A source-level guard test asserts
the assessment card is the last child of every bespoke page's shell so the
order cannot silently regress again.

Consolidate the repeated eight-prop assessment wiring: the five
useInsightStatus-backed pages now mount a shared SlugInsightStatusCard seam
(the sibling of MetricStatusCard for the generic route) instead of
hand-wiring the card and the status?.x defaults inline. The medications page
keeps its inline card because its route carries a richer summary-shaped
envelope than the standard text-only generators. The assessment footer's
"updated" caption now uses the shared relative-time formatter the briefing,
hero, last-measurement and coach-history captions use, so adjacent cards no
longer read one relative and one absolute timestamp.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
(cherry picked from commit 2cd73ce5d6cdca63fea0829990fac2a7e24afebf)
2026-06-05 08:10:12 +02:00
Marc-André Bombeck 0baad1e736 refactor(insights): share one block skeleton across dynamic loaders, anchor the warm-assessments control
The six `next/dynamic` loaders on the insights overview each rendered a
bespoke `<div className="… h-[Xrem] animate-pulse …" />` with a guessed
fixed height. Those heights pinned each placeholder taller or shorter than
the resolved block, so the page shifted as each chunk landed. Route every
loader through one shared `BlockSkeleton` built on the `Skeleton` primitive
(which already honours reduced motion) and hold the row open with a
`min-h` floor rather than a hard-coded pixel guess, removing the
resolve-time layout shift. Decorative placeholders for the cards that can
un-mount stay `aria-hidden`.

Anchor the warm-assessments control too: it floated right-aligned with no
label, reading as a context-less affordance. Pair it with a left-aligned
caption that explains the nightly-refresh model so the button has a home.
2026-06-04 15:36:20 +02:00
Marc-André Bombeck 0e9fa7bf80 fix(insights): drop the question-mark explainer circle from the heading
The round `?` popover next to each category heading read as restless
beside the title and duplicated copy already shown inline. Remove the
`MetricExplainer` trigger entirely; the static one- to two-sentence
definition stays as the muted caption directly beneath the heading, so
the definition is still always visible without a tap. The bundle keeps
the per-metric Title leaf for stable key pairs even though only the Body
caption now renders.
2026-06-01 22:50:59 +02:00
Marc-André Bombeck 36d5c79526 test(insights): pin explainerMetric props against the en.json explainer keys
`<MetricExplainer>` resolves its copy through a template literal
(`insights.subPage.explainer.${metric}{Title,Body}`), so the static
call-site coverage guard never sees these keys — a typo in an
`explainerMetric` prop would paint the raw key path with no CI signal.

Walk every `explainerMetric="…"` value under `src/app/insights/**` plus
the shared healthkit-metric-page scaffold and assert each resolves a
Title and Body leaf in messages/en.json. Also pin the literal-only prop
contract and a non-empty match count so a future rename can't silently
disable the guard. The locale-integrity test then carries the EN
guarantee to the other five bundles.
2026-05-31 22:35:02 +02:00
Marc-André Bombeck 6c7fc7dc84 chore(release): v1.4.45 — analytics 9 s perf fix + audit-marathon closure + v1.4.44 hotfix merged
Audit-marathon close: 14 waves + W10 QA + reconcile. Perf fix (analytics 9 s -> 2-3 s), Zod multi-issue rollout (41 routes), Withings parked-state automation, mobile-UI WCAG 2.5.5 close, QoL i18n + offline banner + account delete, security PII drop + replay-injection scan. Migration 0075 required. Tests 4815 -> 5076.
2026-05-21 23:13:21 +02:00