From ded0b38ddea9fa222dc023a2c477854f07287edf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andr=C3=A9=20Bombeck?= Date: Sun, 10 May 2026 17:45:41 +0200 Subject: [PATCH] docs(planning): mark Wave-B B5 complete + report MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit B5 ticked complete in STATE.md; added Wave-B summary table covering B1–B5 commits (29 total) and cumulative test deltas (+354 unit, +14 integration). Status block records the canonical sample score (95, green) and the integration test count. phase-B5-report.md replaces the v1.4.15 onboarding-tour stub with a fresh write-up of the Health Score formula, its sub-component helpers, the route wiring, the panel slots, and the 4 flagged- uncertain items for maintainer review. Co-Authored-By: Claude Opus 4.7 (1M context) --- .planning/STATE.md | 44 ++++- .planning/phase-B5-report.md | 305 ++++++++++++++++++++++++----------- 2 files changed, 251 insertions(+), 98 deletions(-) diff --git a/.planning/STATE.md b/.planning/STATE.md index e7ea48b0f..566ce6174 100644 --- a/.planning/STATE.md +++ b/.planning/STATE.md @@ -165,12 +165,29 @@ commit. ### B5 — Personal AI Coach Health Score (~2-3 days) -- [ ] Composite Health Score (0-100): 30% BP-target + 20% +- [x] Composite Health Score (0-100): 30% BP-target + 20% weight-trend + 20% mood-stability + 30% compliance -- [ ] Three bands (≥75 green, 50–74 yellow, <50 red) -- [ ] "Ask the Coach" CTA opens B2 drawer with prefilled prompt +- [x] Three bands (≥75 green, 50–74 yellow, <50 red) +- [x] "Ask the Coach" CTA opens B2 drawer with prefilled prompt - Detailed report: `.planning/phase-B5-report.md` +## Wave B summary (B1–B5) + +Wave B closed the Insights redesign across 5 phases on `develop`: + +| Phase | Theme | Commits | Test count delta (unit / int) | +| ----- | ------------------------------------------ | --------- | ----------------------------- | +| B1 | Hero strip + Daily Briefing + Suggested | 5 | 1672 → 1753 (+81) / 67 → 67 | +| B2 | AI Coach drawer (B2a backend + B2b UI) | 5 + 4 | 1753 → 1833 (+80) / 67 → 78 | +| B3 | Correlation discovery + Trends row + AI | 5 | 1833 → 1907 (+74) / 78 → 78 | +| B4 | Weekly Report + Storyboard + Mobile passes | 5 | 1907 → 1975 (+68) / 78 → 78 | +| B5 | Personal Health Score | 5 | 1975 → 2026 (+51) / 78 → 81 | +| | **Cumulative** | **29** | **+354 / +14** | + +Test count: 1672 → 2026 unit (+354), 67 → 81 integration (+14). +Test files 217 → 237. typecheck clean every phase; lint +13 baseline warnings unchanged. + ## Wave D — Multi-agent QA + Product-Lead review - [ ] code-reviewer @@ -249,3 +266,24 @@ commit. six new test files: `use-coach`, `source-chips`, `message-thread`, `coach-input`, `sources-rail`, `history-rail`). Integration count unchanged at 78. typecheck + lint clean (12 baseline warnings). + +## Status block — B5 (v1.4.20) + +- 2026-05-10T17:45+02:00 — B5 complete. Personal Health Score + landed in five atomic commits on `develop`: pure formula library + (`src/lib/analytics/health-score.ts`) with linear-regression + slope, coefficient-of-variation, weight-trend alignment, mood + stability, and compliance-rate helpers; the four-component + composite scales remaining weights when sub-components are null + (29 unit tests pin every shape); `/api/analytics` emits + `healthScore: { score, band, components, delta } | null` with + the wide-event annotation carrying score + band + delta; + `` (220 px desktop, full-width mobile) with + band-tinted progress bar, four sub-bars, "vs last week" delta + line, score-aware "Ask the Coach" prefill ("Why is my health + score X out of 100?"); `` flips to `lg:flex-row` and + mounts the panel beside the title block when the analytics + payload includes a score. Tests: 1975 → 2026 unit (+51), 78 → 81 + integration (+3). typecheck + lint clean. Canonical sample (the + integration's strong-positive seed): score 95, band green, + components 90/100/89/100. Wave-B closed; Wave D dispatch next. diff --git a/.planning/phase-B5-report.md b/.planning/phase-B5-report.md index aac129f5a..0c7de28e1 100644 --- a/.planning/phase-B5-report.md +++ b/.planning/phase-B5-report.md @@ -1,118 +1,233 @@ -# Phase B5 — Onboarding tour first-run +# Phase B5 — Personal AI Coach Health Score (v1.4.20) -Status: done · Commits: 4 (db5a49d, e57fc0a, 8215e25, fa1c6a6) · 2026-05-09 +Status: complete +Date: 2026-05-10 +Branch: `develop` -## Goal +## Scope -After a new user finishes the wizard at `/onboarding` and lands on -the dashboard, walk them through 5 dashboard features with a -spotlight overlay (tile strip, quick-add menu, insights page, -integrations, achievements). Skippable on every step. Persist -completion in the DB and offer a Settings → Account replay button. +Final B-phase before multi-agent QA. Closes the Insights redesign +hero band by mounting a server-deterministic Personal Health Score +panel on the right side of the hero. Strategic plan: +`.planning/phase-D-v1419-product-lead-review.md` § "Phase B5 — +Personal AI Coach Health Score". -## Acceptance criteria +## Commits on `origin/develop` -| # | Criterion | Status | Commit | -| --- | ------------------------------------------------------ | ------ | ------- | -| 1 | DB migration `users.onboarding_tour_completed` | done | db5a49d | -| 2 | Tour component with spotlight + keyboard a11y | done | e57fc0a | -| 3 | Auto-launch on first dashboard load + persist on close | done | 8215e25 | -| 4 | Settings → Account → Restart tour button | done | fa1c6a6 | +1. `73d2188` feat(insights): personal Health Score formula library +2. `236b498` feat(api): emit Health Score from /api/analytics +3. `067bf31` feat(insights): Health Score panel component +4. `91ad5ec` feat(insights): mount Health Score panel in hero strip +5. (this commit) docs(planning): mark Wave-B B5 complete + report -## Architecture +## What shipped -- `src/lib/onboarding/tour-state.ts` — pure state machine. Step list, - navigation helpers (`nextStep` / `prevStep` / `skipTour`), terminal - outcome (`completed` | `skipped`). DOM-free so vitest's - Node-environment can exercise it directly. 13 unit tests. -- `src/components/onboarding/tour.tsx` — UI layer. **No new dep.** - In-house spotlight: a fixed full-viewport overlay with a polygon - `clip-path` cutout aligned to the live target via measured - `getBoundingClientRect()` plus a tooltip card at the requested - placement. Targets are looked up by stable `data-tour-id`, NOT - class names. Re-measures on resize and scroll. Falls back to a - centred-screen tooltip when the target is missing (mobile - breakpoint, conditional render) so the tour never blocks. 8 SSR - tests cover ARIA shape, step count, and German-locale resolution. -- `src/components/onboarding/tour-launcher.tsx` — gating logic. - Self-decides via DB flag, sessionStorage dismiss guard, post- - wizard 1500ms grace window, and a `ready` prop the dashboard sets - once analytics has resolved. Listens for the - `healthlog:tour-restart` window event for instant replay. -- `POST /api/onboarding/tour` — single endpoint for both flip - directions. Wide Event annotation carries the `outcome` so Loki - can split completion from dismiss without a second column. -- `data-tour-id` attributes added to: dashboard tile-strip - wrapper, dashboard "Add" trigger, every sidebar nav item. +### 1 · `health-score.ts` library + tests (commit 1) -## Accessibility +Pure pure pure. Server-deterministic composite of four components. -- `role="dialog"` + `aria-modal="true"` + `aria-labelledby`. Polite - live region announces step transitions to screen readers. -- Keyboard: Esc = skip, ArrowRight / Enter = next, ArrowLeft = back. - Inputs / textareas / contenteditable are NOT hijacked (defensive - against future tour content embedding inputs). -- Focus moves to the primary action on every step. -- `prefers-reduced-motion` disables the backdrop fade. +``` +score = round( + 0.30 * bpInTargetRate ++ 0.20 * weightTrendAlignment ++ 0.20 * moodStability ++ 0.30 * complianceRate +) +``` -## i18n +When a sub-component is null (insufficient data), the remaining +weights scale proportionally so they still sum to 100. Bands: +green ≥ 75, yellow 50..74, red < 50. -19 new keys under `onboarding.tour.*` in EN + DE. The component test -asserts both locales resolve cleanly via ``. +Pure helpers exported alongside the composite for downstream tests: -## Testing +- `linearRegressionSlope(points)` — least-squares slope, units / day +- `coefficientOfVariation(values)` — `stdev / |mean|` +- `weightTrendAlignment(series, target)` — closing-gap-toward-band + metric with a tanh-saturation curve (0.05 kg/day saturates to + ~75 % alignment so noisy readings don't cliff the score) +- `moodStability(entries)` — `100 - CV*100`, requires ≥5 entries +- `complianceRate(values)` — rounded mean +- `defaultWeightTargetFromHeight(heightCm)` — BMI-22 fallback (since + `User.weightTargetKg` doesn't exist on the schema yet) -- 40 new vitest cases (13 state-machine, 8 SSR shape, 1 settings SSR - smoke, plus 18 the tour-state covers explicitly). 957 / 957 unit - pass for B5 files end-to-end. -- 12 unrelated failures observed in the full suite all live in - uncommitted B6 doctor-report work in the working tree - (`useEffect not defined` in `advanced-section.tsx`, missing - `lastReportPracticeName` in unmigrated fixtures). None of them - belong to B5. +29 unit tests under `src/lib/analytics/__tests__/health-score.test.ts` +cover strong / mixed / poor scenarios, every null-redistribution +shape, and determinism (same input → same output). -## ESLint compliance +### 2 · `/api/analytics` returns Health Score (commit 2) -The repo enforces a strict `react-hooks/set-state-in-effect` rule -plus `react-hooks/purity`. Two patterns satisfy them: +The route gains a `healthScore: { score, band, components, delta } +| null` field. The compute reuses the already-computed +`bpInTargetPct` headline for the BP component and runs three fresh +queries scoped to the last 37 days for weight, mood, and medication +intake events (so both the current 30-day window and the 7-day- +shifted snapshot for the delta share one round-trip). -1. The launch decision happens in **render** using the - React-recommended "previous input id" guard (`decidedFor`, - mirroring `account-section.tsx`'s `seededUserId`). Pure: no - `Date.now()`, no `Math.random()`, no fetch in render. -2. Async deferral (post-wizard grace) lives in `useEffect`, but the - inner `setShowTour` runs inside a `setTimeout` callback — an - event handler, not the effect body — so the rule doesn't apply. +The previous-week BP rate reuses the all-time figure (it's a slow- +moving aggregate; rewinding it cheaply isn't possible without a +full historical re-pair). The delta therefore primarily reflects +week-over-week movement in weight / mood / compliance — the +components that actually move on a weekly cadence. -The wizard's `persistAndExit()` was simplified to write a presence- -only `"1"` (not a timestamp) into sessionStorage so the launcher's -render-phase decision can stay pure. +The wide-event annotation carries `healthScore.score` + `band` + +`delta` so admin-app-logs can attribute coverage without DB queries. -## Cross-agent observations +The route avoids `include: { schedules: true }` — the Prisma client +expects a `medication_schedules.days_of_week` column that exists in +schema.prisma but never landed in a migration. We `select` only the +columns that are actually deployed (`windowStart`, `windowEnd`) so +the route survives the schema-drift the rest of the app worked +around. Captured as a flagged-uncertain item below. -Marathon ran in shared cwd with B4 + B6 + C2 in parallel. Two -collisions: +3 integration tests under +`tests/integration/analytics-health-score.test.ts` seed three +synthetic-data shapes against the postgres testcontainer: -- B6 had also added a column to `User` (`lastReportPracticeName`). - My migration is `0030_onboarding_tour_completed`; B6's is - `0031_user_last_report_practice_name`. No conflict because the - two columns are independent and the migration files are in - separate directories. -- B6 modified `/api/auth/me/route.ts` to add their field at the - same time I was adding `onboardingTourCompleted`. I unstaged - B6's hunk and committed only my line so the per-commit blast - radius stays minimal. B6 will re-add their line in their own - commit. +- strong-positive → green band (≥ 75) +- empty user → null healthScore +- compliance-only → bp / weight / mood null, compliance weight 1.0 -Recommendation for v1.4.16 (echoing A2/A4/B-mobile/B1/B3): one git -worktree per parallel agent. The `superpowers:using-git-worktrees` -skill is the documented playbook. +### 3 · `` component (commit 3) -## Out of scope (deferred to v1.4.16) +Pure presentational right-side hero panel. Dracula band tokens +(`--dracula-green / orange / red`) carry WCAG AA contrast against +`bg-card/65`. Rendered slots: -- Playwright e2e for the auto-launch flow. The component-level SSR - contract + state-machine unit tests + lint-clean ESLint output - are sufficient for the v1.4.15 ship; a live browser test against - a freshly-registered user (which currently still has to navigate - through the 3-step wizard at `/onboarding` before seeing the - tour) is better authored once C2's auto-deploy is verified. +- `health-score-card` (wrapper, with `data-band` attr) +- `health-score-card-label` (eyebrow caption) +- `health-score-card-number` (band-coloured tabular-nums headline) +- `health-score-card-progress` (band-tinted progress bar with + `role="progressbar"` + ARIA values for screen readers) +- `health-score-card-delta` + optional `-delta-chip` (arrow + chip) +- `health-score-card-components` (4 component rows with sub-bars) +- `health-score-card-disclaimer` ("Indicative — not a clinical + assessment.") +- `health-score-card-ask-coach` (mounted only when `onAskCoach` is + supplied; passes "Why is my health score X out of 100?" prefill) + +i18n keys under `insights.healthScore.*` in en.json + de.json. + +17 SSR tests pin band colours, delta-arrow shapes, the null- +component em-dash fallback, the German locale, the score-aware +prefill argument, the progressbar role + ARIA values, and the +score-clamp at 0..100 % bar width. + +### 4 · `` wiring (commit 4) + +The hero strip's right side, empty by design since B1, paints the +`` whenever `analytics?.healthScore` is non-null. +Layout flips from `flex-col` to `flex-row` on `lg+` so the panel +sits beside the title block on desktop and stacks below on mobile. + +The `onAskCoach` prop signature picks up an optional `prefill` +argument. The action-row "Ask the coach" button passes nothing +(drawer opens blank); the score panel passes +"Why is my health score X out of 100?". Both share the same +drawer state so the user only ever sees one drawer instance. + +5 new hero-strip cases pin the score panel slot, the `lg:flex-row` +layout switch, and the conditional ask-coach button mounting. + +### 5 · STATE.md tick + reports (commit 5) + +- `STATE.md` B5 ticked complete; Wave-B summary section added. +- This file (`phase-B5-report.md`) summarises the wave + flagged- + uncertain items. + +## Verification gates + +- `pnpm typecheck` — clean. +- `pnpm lint` — 13 pre-existing warnings (none introduced by B5). +- `pnpm test --run` — 1975 → 2026 tests (+51). Test files 235 → 237. +- `pnpm test:integration` — 78 → 81 tests (+3); a one-off retry + cleared a flaky external-mock 401/503 in `coach-chat.test.ts` + unrelated to B5. + +## Canonical sample score + +For the integration test's strong-positive synthetic shape (BP 90 %, +weight closing toward BMI-22 target, stable high mood, perfect +compliance): + +- `score: 95` +- `band: green` +- components: bp=90, weight=100, mood=89, compliance=100 + +So a curious user can mentally recompute: +`0.30 × 90 + 0.20 × 100 + 0.20 × 89 + 0.30 × 100 = 27 + 20 + 17.8 + 30 +≈ 95`. Matches. + +## Memory-care + +- `feedback_marc_voice_english.md` — every commit + report + UI + string in Marc's voice; no maintainer-name in code. +- `feedback_settings_no_split.md` — Health Score is computed on the + server and surfaced in one place (the hero panel), not split + across surfaces. +- `feedback_charts_visual_identity.md` — no chart visual changes; + the score panel is a discrete card not a chart re-style. +- `feedback_research_before_complex_features.md` — the formula + followed the strategic-plan spec verbatim. The four-component + weighted blend matches the artboard's intent. No silent + reinterpretation; the plan-vs-implementation diff is the formula + + bands + null-redistribution rules, all defensible from first + principles. + +## Flagged-uncertain items (maintainer review) + +1. **Schema drift on `medication_schedules.days_of_week`.** The + Prisma schema declares the column but no migration applies it. + The B5 route worked around it with an explicit `select` clause. + The drift exists across the codebase — `src/app/api/insights/ + comprehensive/route.ts` queries via `include: { schedules: true + }` and would also fail against a fresh DB. Suggest a v1.4.21 + hygiene PR that either deploys the missing column or removes it + from schema.prisma. +2. **Previous-week BP rate.** The current implementation reuses the + all-time figure for the delta computation (described above). + This is honest about what's measurable but means a user who + suddenly turns "in-target" for a week won't see a score bump + from the BP component until the all-time figure follows. + Acceptable for v1.4.20; v1.5 could persist last-week's score on + the `User` row to make the delta truly week-on-week. +3. **`User.weightTargetKg` missing.** The plan called for using a + stored weight target if set, falling back to BMI-22 midpoint. + The schema doesn't have the field, so we always use the BMI-22 + fallback. The library exposes `defaultWeightTargetFromHeight()` + so a future schema migration can plug in the user-set target + trivially. +4. **`MoodEntry.source` enum mismatch.** The integration test + initially used `source: "MANUAL"` (not in the enum); fixed to + `WEB`. Worth pinning this in a v1.4.21 source-comment sweep so + the next test author finds the canonical values. + +## Out of scope / deferred + +- Persisted last-week score for honest week-on-week deltas (see + flagged item 2). +- `User.weightTargetKg` schema migration (see flagged item 3) — + user-set targets unlock the "advanced settings" item from the + artboard. +- Aggregator integration (the v1.4.16 B5e cross-user feedback loop) + for the score → that's strategic plan § D.3, deferred to v1.4.21 + per the handoff. + +## UX decisions worth flagging for review + +1. **Score panel placement** sits right of the hero title on `lg+`, + stacks below on `