docs(planning): W3f v1.4.25 four polish wins phase report

Records the four polish-bar deliverables (per-card target-edit cog,
comparison-overlay grey-out, orphan general-status endpoint cleanup,
per-night sleep-stages stacked bar) with their commit SHAs,
file lists, verification results, and notes on the parallel-agent
commit-message collision that swept Win 1 into d74aa20.
This commit is contained in:
Marc-André Bombeck committed 2026-05-14 14:12:15 +02:00
1 parent 7846d68dd5
commit 6f04123982
1 file changed
+231
@@ -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 `<TargetEditSheet>` 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
`<ChartOverlayControlsBody>` 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 `<ChartOverlayControls>` (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 `<sm` and inline NEXT TO the
pill on `sm+` (via `flex flex-col gap-2 sm:flex-row` on the
header right-side container).
- **Win 4 window toggle**: the toggle pills reflow under the
title on `<sm` (`flex flex-col gap-2 sm:flex-row` on the
CardHeader content row).
- All new buttons hit ≥44 px tap target via `min-h-11`.
## Verification
| Command | Result |
|---------|--------|
| `pnpm typecheck` | clean (exit 0) |
| `pnpm lint` | clean (exit 0) — no new errors |
| `pnpm vitest run src/components/charts/__tests__/chart-overlay-controls.test.tsx src/components/targets/ src/components/insights/__tests__/sleep-stage-stacked-bar.test.tsx` | 8 files, 45 tests, 0 failures |
| `pnpm vitest run` (full suite) | 285 files, 2507 tests passing, 6 PRE-EXISTING failures unrelated to my work (Withings new measurement-type enum coverage from a parallel agent — `FAT_MASS`, `MUSCLE_MASS`, `VISCERAL_FAT`, `VASCULAR_AGE`, `SKIN_TEMPERATURE`, `PULSE_WAVE_VELOCITY`, `FAT_FREE_MASS` add 7 enum members; existing tests pinning 18-member shape need updating in a separate phase) |
Manual viewport audit deferred to the v1.4.25 verification phase
where Playwright is runnable.
## Conflict awareness — what happened with parallel agents
The prompt warned about parallel agent activity on:
- W4d (GLP-1 full): src/components/medications/*, snapshot, schema, prompts
- W6 (Dashboard fixes + GLP-1 tile): chart-settings-cog, weight chart
- W9 (Repo + dependabot): README, .github
Observed during my session:
1. **W4d** committed mid-session (commits `2c7c3cc`, `6148a17`,
`f45adb2`, `a68dbdc`, `d74aa20`) and folded my staged Win 1 work
into its own `d74aa20` commit because we both ran `git commit` on
intersecting indexes. See the † note above.
2. **W6** (or a related Withings agent) staged + then committed
Withings extra measurement types + source-priority routes. Six
tests now fail because the enum grew from 18 → 25 members. None
of those failures relate to my Win 1-4 changes.
3. **W6/Withings** also accidentally got swept into my Win 4 commit
(`7846d68`) — the file diff shows 3 withings files alongside my
3 sleep-stages files. The commit subject still accurately
describes the primary change.
i18n parity verified: all four wins append new keys, no restructure.
## Open items
- **Win 1**: per-card cog renders for EVERY card per Marc directive,
but the underlying threshold-edit doesn't exist for derived
metrics (BMI / MOOD_* / MEDICATION_COMPLIANCE /
BLOOD_PRESSURE_IN_TARGET). The dialog handles this by showing an
explanatory caption ("This card is derived from other metrics.
Edit the underlying targets instead.") — the Save button is
disabled in that branch. Acceptable per "consistency rule" but
worth a follow-up if Marc wants the cog to hide on derived cards
instead of explain.
- **Win 1 commit naming**: see † note above. The d74aa20 message
doesn't describe Win 1; the substance is there but discoverability
via `git log --grep` is low. Recommend a v1.4.25 changelog entry
explicitly attributes "per-card edit cog on Zielwerte cards" to
d74aa20 for traceability.
- **Win 4 mobile reflow**: per the prompt's "Bars stack vertically?
No — keep horizontal bars but reduce label density", I kept the
vertical-layout `<BarChart>` 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.