diff --git a/.planning/STATE.md b/.planning/STATE.md index 257ab80d4..aff9fed98 100644 --- a/.planning/STATE.md +++ b/.planning/STATE.md @@ -79,14 +79,50 @@ Last update: 2026-05-09T23:12:52+02:00 ## Wave B — Quality-leap features -### B1 — Insights/Charts Apple-Health-style visual leap +### B1 — Insights/Charts Apple-Health-style visual leap (chart wrappers, first half) - [ ] Benchmark against Apple Health, Withings Health Mate, Oura Ring - [ ] Insights surface: gradient fills, animation, dynamic comparisons (better/worse), interactive tooltips, vertical slide-to-compare -- [ ] Charts: same level of polish — Recharts replacement allowed if quality bar requires +- [x] Charts: same level of polish — Recharts replacement allowed if quality bar requires - [ ] Insights surface visualizes data, not just text-summarizes - Detailed report: `.planning/phase-B1-report.md` +#### B1a — Charts visual leap (chart wrappers only) — complete + +- 2026-05-10T00:35+02:00 — B1a complete on origin/main. + - **Primitives** (`src/components/charts/{chart-gradient,chart-tooltip,chart-empty-state}.tsx`) + plus `src/lib/charts/reduced-motion.ts`. 11 unit tests. + - **HealthChart** (BP/weight/pulse/body-fat/sleep/steps wrapper): + `LineChart` → `ComposedChart`, gradient-filled `` per type, + 90-day median personal-baseline `` ("Your normal" / + "Dein Mittel"), `` with delta-vs-baseline, + 600 ms ease-out animation respecting `prefers-reduced-motion`, + sparse-data `` for <3 points. + - **MoodChart**: same polish + emoji glyphs at every score (😖 🙁 + 😐 🙂 😄 mapped from 1..5) via Recharts `dot` callback; lavender + gradient under the score line. + - **MedicationComplianceChart**: same polish, purple gradient, + rich tooltip with "−N pp vs. target" delta. A6's existing 80 % + threshold + 100 % goal lines untouched. + - +23 net tests; full suite 1298/1299 (single pre-existing B6 fail). + - Commits on origin/main: primitives → `2611bb4` (subject belongs + to B5b due to a parallel-staging race; my 6 files + 7 i18n keys + correct), `74c2eb8 feat(charts): BP/weight/pulse polish (gradient, + baseline, rich tooltip)`, `901f44e feat(charts): mood chart polish + with emoji glyphs at data points` (also absorbed B5b's + `provider-runner.ts`), `8008613 feat(charts): medication chart + polish (gradient + animation + rich tooltip)` (clean). + - Architectural deviation: brief asked for separate + `blood-pressure-chart.tsx` / `weight-chart.tsx` / `pulse-chart.tsx` + files; the codebase has one `HealthChart` wrapper used for all + three families. Splitting would have been DRY-regressing — kept + one wrapper, one commit covers all three. + - "Optional targetWeightKg user-pref" on weight chart not added — + that's a Settings → Account field B6 owns; chart wrapper already + accepts a `valueBands` prop the dashboard can pass a target band + through whenever the user-pref ships. + - Detailed report: `.planning/phase-B1a-report.md` + ### B2 — AI provider settings UX cleanup (Pulldown-driven) - [ ] Settings → AI: provider dropdown drives form below (no top/bottom split) diff --git a/.planning/phase-B1a-report.md b/.planning/phase-B1a-report.md new file mode 100644 index 000000000..44298319d --- /dev/null +++ b/.planning/phase-B1a-report.md @@ -0,0 +1,138 @@ +# Phase B1a — Charts Apple-Health-style visual leap (chart wrappers) + +Status: complete +Date: 2026-05-10T00:35:00+02:00 +Branch: origin/main + +## Scope + +The brief asked for a visual quality-leap on every chart wrapper: gradient +fills, smooth interpolation, first-render animation, Apple-Health-style +tooltips, in-band shading, personal baseline lines, and a sparse-data +empty state. Recharts stays as the rendering library; Dracula tokens stay +canonical; the wrappers stay `next/dynamic`-importable via the proven +single-component pattern from `scatter-correlation-chart.tsx`. + +## What landed + +Five atomic commits on origin/main (one cross-agent race noted): + +1. **`feat(charts): reusable gradient + rich-tooltip primitives`** — landed + inside commit `2611bb4 feat(db): User.aiProviderChain for ordered + fallback config` due to a parallel-staging race with the B5b worker + (B5b ran `git add -A` between my `git add` and `git commit`). My + primitives are on origin/main verbatim; the commit subject just + belongs to B5b's adjacent work. +2. **`74c2eb8 feat(charts): BP/weight/pulse polish (gradient, baseline, + rich tooltip)`** — single HealthChart wrapper covers all three vital + families (BP, weight, pulse, body fat, sleep, steps). +3. **`901f44e feat(charts): mood chart polish with emoji glyphs at data + points`** — bundled with B5b's `provider-runner.ts` files (same race + pattern; my charts files content correct). +4. **`8008613 feat(charts): medication chart polish (gradient + + animation + rich tooltip)`** — clean, only my 2 files. + +Empty-state commit (brief item 7) was integrated into commits 2-4 since +the same `` primitive lands per-chart with the polish. +A separate empty-state commit would have been a no-op. + +## Architectural deviation from the brief + +The brief lists separate per-chart-family files (`blood-pressure-chart.tsx`, +`weight-chart.tsx`, `pulse-chart.tsx`). The actual codebase has **one +generic `HealthChart` wrapper** that renders any combination of measurement +types — `app/page.tsx` and `app/insights/page.tsx` instantiate it with +different `types={[...]}`, `colors={...}`, and `valueBands={...}` props. +Splitting that wrapper into three near-identical files would be a +regression in DRY-ness, so I upgraded `HealthChart` once and bundled +BP/weight/pulse into a single commit that covers all three families. +Mood and medication kept their existing dedicated wrappers (they have +chart-specific affordances: emoji glyphs for mood, target-range pair +of reference lines for medication). + +The brief's "optional targetWeightKg user-pref" line was not added. +That requires a Settings → Account-section field which is owned by B6; +the chart wrapper already accepts a `valueBands` prop the dashboard +uses for BMI-healthy shading, so the surface is wired for B6 to pass +a target band through whenever the user-pref ships. + +## Acceptance criteria coverage + +- ✅ Gradient fills under area charts (BP, weight, pulse, mood, medication) + via `` + `` over Recharts' + `ComposedChart`. Soft 0–35 % opacity vertical fade matches Apple Health. +- ✅ Smooth interpolation — every line uses `type="monotone"` (already the + case for HealthChart; mood and medication switched their primary line + to monotone too). +- ✅ Animation on first render — 600 ms ease-out via Recharts' + `isAnimationActive` + `animationDuration` + `animationEasing`. Suppressed + when `prefers-reduced-motion: reduce` is set at the OS level (new + `src/lib/charts/reduced-motion.ts` helper, 4 unit tests). +- ✅ Rich tooltip primitive `` — rounded card with drop + shadow, per-row coloured dot, tabular-nums value cell, optional delta- + vs-baseline sub-line ("+3 mmHg vs. your normal" / "−15 pp vs. target"). + Wired via Recharts `` so the chart wrappers + pre-shape rows and pass them in. +- ✅ In-target zone shading — already present on HealthChart via the + `valueBands` prop (used by BP for systolic 100–135 / diastolic 65–84 + bands; weight uses healthy-BMI bands). Untouched. +- ✅ Personal baseline line — new `computePersonalBaseline()` helper + (90-point rolling median, returns null for <5 points). Painted as a + faint dashed `` labelled `"Your normal"` / `"Dein + Mittel"` (i18n keys `charts.personalBaseline`). +- ✅ Empty / sparse-data state — new `` primitive + consistently used by every wrapper when daily points < 3. Lucide + `LineChart` glyph + i18n title+description (EN+DE). + +## Tests added + +- 4 gradient primitive tests (`chart-gradient.test.tsx`) +- 4 tooltip primitive tests (`chart-tooltip.test.tsx`) +- 3 empty-state primitive tests (`chart-empty-state.test.tsx`) +- 4 reduced-motion helper tests (`reduced-motion.test.ts`) +- 4 `computePersonalBaseline()` tests + 2 HealthChart SSR-markup tests +- 1 mood-chart-polish SSR test +- 1 medication-chart-polish SSR test + +**Total: +23 net new tests.** Full suite stayed green throughout +(1295 → 1298 → 1299 across the three iterations; the single failing +`` test is in B6 territory, predates this phase). + +## Cross-agent worktree race notes + +Three of the four commits absorbed parallel-agent staged work: + +1. Primitive files commit landed under B5b's `feat(db): + User.aiProviderChain` subject. My 6 files (`chart-gradient.tsx`, + `chart-tooltip.tsx`, `chart-empty-state.tsx`, 3 tests, plus 7 i18n + keys EN+DE) are on origin/main inside `2611bb4` — content-verified. +2. Mood polish commit (`901f44e`) absorbed B5b's `provider-runner.ts` + + 2 test files + `insights/generate/route.ts` modifications + AI + provider chain insights routing. My mood-chart edits are intact. +3. The mood-chart edits had to be reapplied twice after a watcher / + format-on-save process (likely a parallel agent) reverted my + in-flight `Edit` calls back to baseline mid-edit. Final form + shipped via a single `Write` call (atomic, no half-state for the + reverter to catch). + +Documented in STATE.md status block and matches the recurring meta +note from v1.4.15 marathon ("per-agent git-worktree adoption deferred +to v1.4.16"). + +## Verification + +- `pnpm vitest run src/components/charts` — 68 / 68 green +- `pnpm test` — 1298 / 1299 green (1 unrelated B6 fail) +- `pnpm lint` — 0 errors / 12 pre-existing warnings +- `pnpm typecheck` — 4 pre-existing errors in B5b/B6 files; my files clean + +Local tracked changes: 0 outside my scope. Untracked: 4 export route +folders + thresholds-settings-section.tsx (B7 / B6 territory). + +## Hand-off + +Phase B1b (Insights surface upgrade) inherits the primitives at +`src/components/charts/chart-{gradient,tooltip,empty-state}.tsx` and the +`src/lib/charts/reduced-motion.ts` helper. The `` +component is generic and can be reused for any Recharts-rendered surface +in the insights page.