diff --git a/.planning/phase-W3f-v1425-polish-wins-report.md b/.planning/phase-W3f-v1425-polish-wins-report.md new file mode 100644 index 000000000..624747607 --- /dev/null +++ b/.planning/phase-W3f-v1425-polish-wins-report.md @@ -0,0 +1,231 @@ +# Phase W3f v1.4.25 — Four polish wins + +Author: implementation agent +Status: shipped, develop only (no push, no tag, no version bump) +Date: 2026-05-14 +Branch: `develop` + +## Summary + +Closed four polish backlog items from the v1.4.25 Marc directive set: + +1. **Per-card edit cog on Zielwerte cards** — closes the W3e deferred + item; consistency rule across Dashboard → Insights → Zielwerte. +2. **Comparison-overlay grey-out** — explicit disabled state with + `aria-disabled`, `opacity-50`, native tooltip, footer hint copy + when the prior period carries no overlay-able rows. +3. **Orphan endpoint cleanup** — drops + `/api/insights/general-status/route.ts`; the route had zero UI + callers since v1.4.16. The library helper stays for the reminder + worker. +4. **Per-night sleep-stages stacked bar** — rewrites the + 30-day average composition into a per-night column chart with a + 7/14/30d window toggle. Default 7d. + +All four wins ship as atomic commits, no `Co-Authored-By` trailer, +no push, no version bump. + +## Per-win commit SHAs + +| Win | SHA | Subject | +|---:|-----|---------| +| 1 | `d74aa20`† | feat(targets): per-card edit cog opens target-config sheet | +| 2 | `7b6b916` | style(charts): grey out comparison-overlay toggle when no prior-period data exists | +| 3 | `edae569` | chore(insights): drop orphaned /api/insights/general-status — superseded by InsightAdvisorCard since v1.4.16 | +| 4 | `7846d68` | feat(insights): sleep-stages chart renders per-night stacked bars (7/14/30d toggle) | + +† **Win 1 commit-message collision.** While I was staging Win 1, a +parallel agent (W4d GLP-1) staged + committed before my `git commit` +landed. Git folded my staged target work into the W4d agent's commit +(`d74aa20 style(glp1): prettier formatting across W4d surfaces`). +The author + email match Marc's identity, so the commit is +unrevertible without rewriting the W4d agent's history. The +**actual contents** of `d74aa20` include all of Win 1's files: +`src/components/targets/target-edit-sheet.tsx` (new, 459 LOC), +`src/components/targets/target-card.tsx` (cog wiring, +109 −0 LOC), +`src/components/targets/__tests__/target-edit-sheet.test.tsx` (new, +198 LOC), `src/components/targets/__tests__/target-card.test.tsx` +(cog assertions, +57 LOC). Verified by `git show d74aa20 -- src/components/targets/`. +The "style(glp1) prettier" framing is misleading — the commit is +mostly Win 1 substance with a small GLP-1 prettier pass. Documented +here so the v1.4.25 changelog can attribute the work correctly. + +## Files touched + +### Win 1 (in `d74aa20`) + +- `src/components/targets/target-edit-sheet.tsx` (NEW, ~340 LOC after + the post-lint setState-in-useEffect refactor) +- `src/components/targets/target-card.tsx` — adds `Settings2` cog + button top-right of every card, threads `editOpen` boolean state + + mounts `` at the card root +- `src/components/targets/__tests__/target-card.test.tsx` — 4 new + cases under "per-card edit cog (v1.4.25 W3f)" +- `src/components/targets/__tests__/target-edit-sheet.test.tsx` (NEW) + — 6 cases including the focus-management contract test +- `messages/en.json` + `messages/de.json` — `targets.edit.*` block + (openLabel, title, description, derivedHint, minLabel, maxLabel, + systolicMin/Max, diastolicMin/Max, boundsHint, resetToDefault, + saveSuccess, saveError, resetSuccess) + +### Win 2 + +- `src/components/charts/chart-overlay-controls.tsx` — extracted + `` so the popover body is testable + without Radix's portal; new `hasComparisonData?: boolean` prop + defaulting to `true`; grey-out styling (`opacity-50` + + `aria-disabled` + `title=tooltip` + footer hint paragraph) when + `comparisonBaseline !== "none" && !hasComparisonData` +- `src/components/charts/health-chart.tsx` — threads + `hasComparisonData` into `` (the memo already + existed at line 768) +- `src/components/charts/mood-chart.tsx` — same threading +- `src/components/charts/medication-compliance-chart.tsx` — passes + `hasComparisonData={false}` unconditionally (the compliance heatmap + never paints a prior-period overlay; already documented in the + existing N/A caption) +- `src/components/charts/__tests__/chart-overlay-controls.test.tsx` + — 5 new cases under "comparison-baseline grey-out (v1.4.25 W3f)" +- `messages/en.json` + `messages/de.json` — appended + `chart.overlay.controls.comparisonUnavailable` + +### Win 3 + +- `src/app/api/insights/general-status/route.ts` (DELETED, 49 LOC) +- The lib helper `src/lib/insights/general-status.ts` stays — the + reminder worker `src/lib/jobs/reminder-worker.ts` still pre-warms + the daily cache via `generateGeneralStatusForUser()`. The HTTP + route was the only orphan. +- OpenAPI registry (`src/lib/openapi/routes.ts`) and + `docs/api/openapi.yaml` already carried no general-status path, so + the regenerate step was a no-op. + +### Win 4 + +- `src/app/api/analytics/route.ts` — + `computeSleepStageBreakdown()` now returns a `perNight: Array<{ + dayKey, stages }>` field alongside the existing 30-day aggregate. + Sorted ascending by Berlin-tz day key; empty days drop out. +- `src/components/insights/sleep-stage-stacked-bar.tsx` — rewritten. + Per-night BarChart with `stackId="stages"`, vertical layout + (per Marc directive — no horizontal reflow on mobile, instead the + XAxis uses `interval="preserveStartEnd"` to thin labels). 7/14/30d + window-toggle buttons in the card header (same `min-h-11` touch + target as the dashboard range tabs). Y-axis renders hours. Tooltip + shows per-stage minutes + percent + nightly total. Empty-state + branch renders `data-slot="sleep-stage-empty"` with the existing + `insights.sleep.stages.unavailable` copy. Legacy fallback: when + `perNight` is absent (rollout phase), the chart still renders the + aggregate as a single column so the user doesn't see a blank card. +- `src/components/insights/__tests__/sleep-stage-stacked-bar.test.tsx` + — 4 new cases under "per-night + window toggle (v1.4.25 W3f)"; + the original 4 tests keep passing (updated with `perNight` field + in fixtures). + +## Skills invoked + impact + +1. **`Skill: frontend-design`** — drove the polish bar: + - **Win 1 cog**: matches the Dashboard chart-cog visual rhythm + (`Settings2` icon, `h-3.5 w-3.5`, `min-h-11 min-w-11 px-0`, + `text-muted-foreground hover:text-foreground`). + - **Win 2 grey-out**: uses `opacity-50` instead of the visually + louder `disabled:opacity-50` from the button base class — the + buttons remain clickable (so the user can click to re-evaluate + when fresh data arrives) but read as disabled. + - **Win 4 chart**: per-bar height = 220px (vs. 120px aggregate) + to give the stacked columns room to communicate; XAxis labels + thin via `interval="preserveStartEnd"` so a 30-day window stays + readable on a 393 px Pixel-5 viewport. + +2. **`Skill: mobile-first-design`** — drove the breakpoint + architecture: + - **Win 1 cog**: top-right placement on every viewport; the cog + stacks BENEATH the status pill on `` with `interval="preserveStartEnd"` + on the XAxis. The 30-day window on a 393 px viewport shows roughly + 6 labels (start + end + ~4 evenly-spaced ticks). Visually verified + via the test SSR snapshots; Playwright sweep across viewports + queued for v1.4.25 verification. +- **Win 4 integration test**: the analytics endpoint's new + `perNight` field has no integration test. The project has no + existing integration test scaffold for `/api/analytics` (verified + by `find src/app/api/analytics -name "*test*"`); adding one needs + a Prisma fixture for a stage-tagged sleep series. Filed as a + v1.4.25 backlog item. + +## Anything not fully wired + +- Win 1: derived-metric branch shows an explanatory caption but + doesn't deep-link to the underlying editable target. A power user + on the BMI card who wants to edit the WEIGHT range still has to + close the dialog and open it on the WEIGHT card. Acceptable for + v1.4.25; deep-link follow-up belongs in v1.4.26. +- Win 4: the empty-state branch reuses the existing + `insights.sleep.stages.unavailable` copy ("No stage data yet — + Apple Health stage syncing arrives in v1.5"). When the API + starts returning empty `perNight: []` for an account that DOES + have aggregate stage data (impossible per the current helper but + defensible as a guardrail), the fallback to the aggregate row + paints; the empty caption only fires when both `perNight` AND + `stages` are empty.