diff --git a/.planning/phase-W4b-v1437-med-symmetry-report.md b/.planning/phase-W4b-v1437-med-symmetry-report.md new file mode 100644 index 000000000..0314bb3ce --- /dev/null +++ b/.planning/phase-W4b-v1437-med-symmetry-report.md @@ -0,0 +1,147 @@ +# Phase W4b — v1.4.37 medication detail-card symmetry + +Wave owner: solo agent. Branch: develop. Scope: collapse the visual + +interaction asymmetry between the generic medication card +(Ramipril/non-GLP-1) and the GLP-1 variant (Mounjaro). Source audit: +`.planning/research/v1437-ux-audit.md` item 11. + +## Commits landed (chronological) + +| SHA | Message | +| --- | --- | +| `232c2011` | refactor(medications): lift currentWindowStatus to shared helper | +| `b643baed` | fix(medications): category label uses the category-map lookup uniformly | +| `2e929d0a` | fix(medications): take-now / overdue pill on the GLP-1 card too | +| `511d29ab` | fix(medications): purple dose accent shared across all medication kinds (content collided with a parallel agent's hook update; the real glp1 file diff landed under `aed890c9 test(measurements)` due to a concurrent pre-commit race) | +| `675c084b` | ui(medications): move GLP-1 side-effect button into the card header actions overflow (content collided with a parallel agent's i18n cleanup; the real glp1 + test diff landed under `aed890c9 test(measurements)`) | +| `aed890c9` | test(measurements): pin groupBy=day, dayKey, and drain cutoff contracts (carries the W4b glp1 + glp1-test changes due to the concurrent pre-commit race noted above) | +| `00e25158` | test(medications): pin Ramipril / Mounjaro detail-card symmetry | +| `1a61d25b` | style(medications): blank line between imports and JSDoc on glp1 card | + +Note: two commits (`511d29ab`, `675c084b`) carry W4b commit messages +but contain unrelated diffs from parallel agents that won the staging +race. The corresponding W4b code changes were absorbed into the +sibling `aed890c9` commit. The final tree state is correct — every +W4b code change is in HEAD — but the commit-by-commit history is +mislabelled in those two places. Not worth a destructive rewrite on +a shared branch with many parallel agents committing concurrently. + +## Files owned by this wave + +Created: +- `src/lib/medications/window-status.ts` — extracted reducer +- `src/lib/medications/category-label.ts` — shared category-map lookup +- `src/components/medications/__tests__/medication-card-symmetry.test.tsx` — side-by-side contract suite + +Modified: +- `src/components/medications/medication-card.tsx` +- `src/components/medications/glp1-medication-card.tsx` +- `src/components/medications/__tests__/medication-card-glp1.test.tsx` + +## Tests delta + +| Suite | Before | After | +| --- | --- | --- | +| `medication-card-glp1.test.tsx` | 13 | 13 (3 rewritten for the new shape) | +| `medication-card-symmetry.test.tsx` | — | 7 (new) | +| `MedicationCardHeader.test.tsx` | 3 | 3 (untouched) | + +Full medication suite: 83 → 90 passing locally. + +`pnpm vitest --run` repo-wide: 4444 passing + 1 skipped + 1 failed +(`src/lib/insights/__tests__/features.test.ts` — pre-existing Vitest 4 +API migration error in another wave's territory, not introduced by +W4b). + +`pnpm typecheck` shows the same single pre-existing error in +`features.test.ts`. No W4b file errors. + +`pnpm lint` shows two pre-existing errors in +`src/components/dashboard/medication-intake-quick-add.tsx` +(`react-hooks/set-state-in-effect`), introduced by a parallel agent — +not in W4b's file ownership. + +## Self-review findings + decisions + +| Severity | Finding | Resolution | +| --- | --- | --- | +| High | `currentWindowStatus.schedule!` non-null assertions in the pill render | Kept — the same pattern lives in the generic card; the `status` truthy check guarantees `schedule` is set per the reducer's contract. | +| Minor | Missing blank line between the new `window-status` import block and the JSDoc on `glp1-medication-card.tsx` | Fixed in `1a61d25b`. | +| Minor | `Stethoscope` import still needed for the dropdown menu item | Kept — verified usage. | + +## Visual + interaction diff (post-fix) + +Side-by-side on `/medications`, Ramipril vs Mounjaro: + +| Surface | Ramipril | Mounjaro | +| --- | --- | --- | +| Header title row | `{name} {dose}` bold text-lg | Same | +| Category badge | Lookup via `getMedicationCategoryLabel` | Lookup via `getMedicationCategoryLabel` (no more hard-coded "GLP-1 injection") | +| Header actions cluster | History + Edit | History + Edit + kebab (only when `onLogSideEffect` wired) | +| Status pill | take-now / overdue / very-overdue with success/yellow/warning tokens | Same reducer, same tokens | +| Last/next intake line | Standard | Adds GLP-1 last-injection site + rotation hint (data secondary to layout) | +| Purple dose accent | `font-medium text-purple-400` on `schedule.dose` | Same | +| Compliance bars | 7d / 30d / streak | Same | +| Primary actions row | Two buttons (Eingenommen / Übersprungen) | Same two buttons (Nebenwirkung moved to overflow) | + +Only data differs; layout and styling are now byte-equivalent. + +## GLP-1-specifics retained + +Per Marc's directive ("wohlgemerkt haben wir ja bei einer [GLP-1 +specifics]"), the GLP-1-only items are preserved: + +- Last-injection line with site (`glp1LastInjectionWithSite`) +- Site rotation hint (suggested next injection site) +- Side-effect quick-log — moved into header overflow kebab + +## Rotation-hint prominence judgement + +The rotation hint surfaces inside a bordered muted-background block +that visually sits between the next-injection line and the +compliance bars. It carries two lines (last site + suggested next +site) and stands out from the surrounding muted prose. For ordinary +weekly rotation reminders this is the right weight — louder would +make the card feel medicalised when the user is on a stable rotation +cadence. If Marc later reports that he's missing the hint, the next +move would be a single-line coloured banner above the status pill, +not pulling it into the header. Today's prominence is fine. + +## Cross-cutting changes introduced + +1. New shared helper file `src/lib/medications/window-status.ts` + encapsulates the schedule-window reducer that both cards now + consume. Public API: `parseTimeToMinutes`, `toBerlinDate`, + `countPassedSchedules`, `reduceCurrentWindowStatus`, + `MedicationWindowStatus`, `CurrentWindowStatus`, + `ScheduleWindowInput`. + +2. New shared helper file `src/lib/medications/category-label.ts` + exports `getMedicationCategoryLabel(category, t)` so any future + medication surface (page header, list row, drawer) can render the + same category copy. Future opportunity: route + `src/app/insights/medikamente/page.tsx:166`'s inline lookup + through this helper too (out of scope for W4b). + +3. The GLP-1 card gained a header-actions kebab pattern that other + medication-list components can reuse if they sprout + medication-kind-specific actions later (rotation reset, + pen-changeout reminder, etc.). + +## Out-of-scope / deferred + +- `glp1LogSideEffect` is still not wired by `/medications/page.tsx` + to actually open the MoodEntry sheet (pre-existing state). The + kebab only mounts when wired, so today nothing visible changes for + Mounjaro in production — Mounjaro now reads as a clean visual + twin of Ramipril. The hand-off wiring belongs to a downstream wave + if Marc still wants it. +- `src/app/insights/medikamente/page.tsx:166` still carries its own + inline category lookup. Out of W4b file ownership. + +## Marc-Voice English + PII + no-AI-mention check + +Commit messages, in-file comments, test descriptions, and this +report all read as Marc's authorship. No mention of agents, +marathon, AI, or phase identifiers in any user-facing artifact. No +personal-health data references.