From 2d111fcb24a9f820ede87c0a5d6738ce6660f646 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andr=C3=A9=20Bombeck?= Date: Sat, 16 May 2026 03:17:00 +0200 Subject: [PATCH] chore(release): v1.4.28 Bug-fix and consistency follow-through after the v1.4.27 mobile sweep. The headline is a tighter dashboard and insights surface: six widgets retire from code entirely (GLP-1 tile, dashboard `` mount, GLP-1 detail-page intake history + inventory, the `` block and its regeneration affordance, the weekly-report route), four broken edges close (workout-edit duplicate- timestamp 409, BD-Zielbereich tile rebuilt on the shared `` primitive, `/insights/puls` chart timeout fallback, sticky tab-strip scroll lock), and one consistency contract lands per maintainer directive: every medication-list row, every medication- detail section header, every trend tile, every Coach launch glyph, every chart-height contract reads on one shape. The HealthScore card grows to fill its column, gains an accessible `?` tooltip explaining the delta, and drops the placeholder "Wochenbericht erstellen" button. iOS contracts are intact; the only `/api/*` evolution is additive (the `aggregate=monthly` grain on `/api/measurements` and an internal-only `/api/internal/web-vitals` beacon route). --- .planning/round-5-release-closure-report.md | 158 ++++ .planning/v1428-feedback-2026-05-15.md | 211 +++++ .planning/v1428-marathon-kickoff.md | 178 ++++ CHANGELOG.md | 331 +++++++ docs/api/openapi.yaml | 18 +- e2e/dashboard.spec.ts | 102 +- e2e/insights-advisor-card.spec.ts | 245 ----- e2e/insights-scroll-restoration.spec.ts | 91 ++ e2e/v1427-glp1-tile.spec.ts | 205 ---- messages/de.json | 103 +- messages/en.json | 103 +- messages/es.json | 105 +-- messages/fr.json | 105 +-- messages/it.json | 105 +-- messages/pl.json | 105 +-- next.config.ts | 15 +- package.json | 4 +- pnpm-lock.yaml | 128 +++ src/__tests__/i18n-drift-guard.test.ts | 17 +- src/app/__tests__/insights-polish.test.ts | 45 +- src/app/__tests__/targets-responsive.test.tsx | 9 +- src/app/api/dashboard/glp1/route.ts | 203 ---- src/app/api/insights/generate/route.ts | 4 +- .../web-vitals/__tests__/route.test.ts | 151 +++ src/app/api/internal/web-vitals/route.ts | 135 +++ src/app/api/measurements/[id]/route.ts | 38 +- .../__tests__/put-duplicate-timestamp.test.ts | 199 ++++ .../__tests__/range-aggregation-route.test.ts | 218 +++++ src/app/api/measurements/route.ts | 69 +- src/app/insights/blutdruck/page.tsx | 65 +- src/app/insights/bmi/page.tsx | 69 +- src/app/insights/gewicht/page.tsx | 61 +- src/app/insights/layout.tsx | 8 + src/app/insights/medikamente/page.tsx | 51 +- src/app/insights/page.tsx | 43 +- src/app/insights/puls/page.tsx | 63 +- src/app/insights/report/[week]/page.tsx | 40 - src/app/insights/schlaf/page.tsx | 63 +- src/app/insights/stimmung/page.tsx | 24 +- src/app/layout.tsx | 2 + src/app/medications/[id]/history/page.tsx | 48 +- src/app/page.tsx | 81 +- .../charts/__tests__/bd-tile.test.tsx | 117 +++ .../__tests__/health-chart-range.test.tsx | 107 +++ .../__tests__/mood-chart-polish.test.tsx | 25 + .../__tests__/trend-card-all-time.test.tsx | 119 --- .../charts/__tests__/trend-card.test.tsx | 8 +- src/components/charts/chart-skeleton.tsx | 60 ++ .../charts/health-chart-dynamic.tsx | 29 + src/components/charts/health-chart.tsx | 123 ++- src/components/charts/mood-chart.tsx | 44 +- src/components/charts/trend-card.tsx | 49 +- .../dashboard/__tests__/glp1-tile.test.tsx | 436 --------- src/components/dashboard/glp1-tile.tsx | 497 ---------- .../__tests__/health-score-card.test.tsx | 68 ++ .../health-score-delta-explainer.test.tsx | 201 ++++ .../insights/__tests__/hero-strip.test.tsx | 135 +-- .../__tests__/insight-advisor-card.test.tsx | 323 ------- .../insight-citation-footnote.test.tsx | 164 ---- ...ation-card-confidence-integration.test.tsx | 179 ---- .../recommendation-card-integration.test.tsx | 193 ---- .../recommendations-grid-integration.test.tsx | 170 ---- .../__tests__/trend-annotation.test.tsx | 25 + .../insights/__tests__/trends-row.test.tsx | 22 + .../__tests__/weekly-report-view.test.tsx | 150 --- .../insights/coach-launch-button.tsx | 77 +- .../__tests__/mobile-rail-tray.test.tsx | 108 +++ .../insights/coach-panel/coach-drawer.tsx | 118 +-- .../insights/coach-panel/mobile-rail-tray.tsx | 87 ++ src/components/insights/daily-briefing.tsx | 7 +- src/components/insights/health-score-card.tsx | 40 +- .../insights/health-score-delta-explainer.tsx | 160 ++++ src/components/insights/hero-strip.tsx | 202 +--- .../insights/insight-advisor-card.tsx | 776 --------------- .../insights/insights-tab-strip.tsx | 12 +- src/components/insights/layout-coach-fab.tsx | 51 + .../insights/metric-empty-state.tsx | 73 ++ .../insights/sleep-duration-chart.tsx | 21 +- src/components/insights/sub-page-shell.tsx | 28 +- src/components/insights/trend-annotation.tsx | 16 +- src/components/insights/trends-row.tsx | 110 ++- .../insights/use-insights-advisor.ts | 6 +- src/components/insights/vo2-max-chart-row.tsx | 22 +- .../insights/weekly-report-view.tsx | 324 ------- .../measurements/measurement-list.tsx | 24 +- src/components/medications/DrugLevelChart.tsx | 216 ++--- .../medications/MedicationCardHeader.tsx | 79 ++ .../medications/SchedulingSection.tsx | 15 +- .../medications/SideEffectsSection.tsx | 64 +- .../medications/TitrationSection.tsx | 23 +- .../__tests__/DrugLevelChart.test.tsx | 23 + .../__tests__/MedicationCardHeader.test.tsx | 91 ++ .../__tests__/SideEffectsSection.test.tsx | 44 +- .../__tests__/medication-card-glp1.test.tsx | 57 +- .../medications/glp1-medication-card.tsx | 204 ++-- .../medications/intake-history-list.tsx | 886 ------------------ .../medications/inventory-section.tsx | 549 ----------- .../medications/medication-card.tsx | 107 ++- .../medications/medication-detail-section.tsx | 26 +- .../monitoring/web-vitals-reporter.tsx | 52 + .../__tests__/target-coach-button.test.tsx | 31 +- src/components/targets/target-card.tsx | 19 +- .../targets/target-coach-button.tsx | 29 +- src/hooks/use-insight-status.ts | 5 + src/hooks/use-insights-analytics.ts | 87 ++ .../ai/__tests__/coach-prompt-v423.test.ts | 45 +- .../ai/__tests__/weekly-report-schema.test.ts | 318 ------- .../__tests__/coach-prompt-locale.test.ts | 1 - src/lib/ai/prompts/insight-generator.ts | 100 +- src/lib/ai/prompts/native-prompts.ts | 8 - src/lib/ai/schema.ts | 78 -- .../insights/__tests__/chart-tokens.test.ts | 10 +- .../insights/__tests__/glp1-plateau.test.ts | 4 +- .../__tests__/pulse-status-timeout.test.ts | 77 ++ src/lib/insights/__tests__/week-iso.test.ts | 75 -- src/lib/insights/blood-pressure-status.ts | 93 +- src/lib/insights/bmi-status.ts | 71 +- src/lib/insights/coach-launch-context.tsx | 19 +- src/lib/insights/general-status.ts | 91 +- src/lib/insights/glp1-plateau.ts | 13 +- .../insights/medication-compliance-status.ts | 44 +- src/lib/insights/mood-status.ts | 98 +- src/lib/insights/pulse-status.ts | 73 +- src/lib/insights/week-iso.ts | 86 -- src/lib/insights/weight-status.ts | 91 +- src/lib/insights/with-timeout.ts | 71 ++ .../__tests__/range-aggregation.test.ts | 145 +++ src/lib/measurements/range-aggregation.ts | 147 +++ src/lib/medications/glp1-pk.ts | 10 +- .../medications/inventory/state-machine.ts | 9 +- .../dispatch-localised-cache.test.ts | 165 ++++ src/lib/notifications/dispatch-localised.ts | 69 +- src/lib/openapi/routes.ts | 1 - src/lib/query-keys.ts | 15 +- src/lib/validations/measurement.ts | 12 +- src/types/analytics.ts | 23 + 136 files changed, 5731 insertions(+), 8462 deletions(-) create mode 100644 .planning/round-5-release-closure-report.md create mode 100644 .planning/v1428-feedback-2026-05-15.md create mode 100644 .planning/v1428-marathon-kickoff.md delete mode 100644 e2e/insights-advisor-card.spec.ts create mode 100644 e2e/insights-scroll-restoration.spec.ts delete mode 100644 e2e/v1427-glp1-tile.spec.ts delete mode 100644 src/app/api/dashboard/glp1/route.ts create mode 100644 src/app/api/internal/web-vitals/__tests__/route.test.ts create mode 100644 src/app/api/internal/web-vitals/route.ts create mode 100644 src/app/api/measurements/__tests__/put-duplicate-timestamp.test.ts create mode 100644 src/app/api/measurements/__tests__/range-aggregation-route.test.ts delete mode 100644 src/app/insights/report/[week]/page.tsx create mode 100644 src/components/charts/__tests__/bd-tile.test.tsx create mode 100644 src/components/charts/__tests__/health-chart-range.test.tsx delete mode 100644 src/components/charts/__tests__/trend-card-all-time.test.tsx create mode 100644 src/components/charts/chart-skeleton.tsx create mode 100644 src/components/charts/health-chart-dynamic.tsx delete mode 100644 src/components/dashboard/__tests__/glp1-tile.test.tsx delete mode 100644 src/components/dashboard/glp1-tile.tsx create mode 100644 src/components/insights/__tests__/health-score-delta-explainer.test.tsx delete mode 100644 src/components/insights/__tests__/insight-advisor-card.test.tsx delete mode 100644 src/components/insights/__tests__/insight-citation-footnote.test.tsx delete mode 100644 src/components/insights/__tests__/recommendation-card-confidence-integration.test.tsx delete mode 100644 src/components/insights/__tests__/recommendation-card-integration.test.tsx delete mode 100644 src/components/insights/__tests__/recommendations-grid-integration.test.tsx delete mode 100644 src/components/insights/__tests__/weekly-report-view.test.tsx create mode 100644 src/components/insights/coach-panel/__tests__/mobile-rail-tray.test.tsx create mode 100644 src/components/insights/coach-panel/mobile-rail-tray.tsx create mode 100644 src/components/insights/health-score-delta-explainer.tsx delete mode 100644 src/components/insights/insight-advisor-card.tsx create mode 100644 src/components/insights/layout-coach-fab.tsx create mode 100644 src/components/insights/metric-empty-state.tsx delete mode 100644 src/components/insights/weekly-report-view.tsx create mode 100644 src/components/medications/MedicationCardHeader.tsx create mode 100644 src/components/medications/__tests__/MedicationCardHeader.test.tsx delete mode 100644 src/components/medications/intake-history-list.tsx delete mode 100644 src/components/medications/inventory-section.tsx create mode 100644 src/components/monitoring/web-vitals-reporter.tsx create mode 100644 src/hooks/use-insights-analytics.ts delete mode 100644 src/lib/ai/__tests__/weekly-report-schema.test.ts create mode 100644 src/lib/insights/__tests__/pulse-status-timeout.test.ts delete mode 100644 src/lib/insights/__tests__/week-iso.test.ts delete mode 100644 src/lib/insights/week-iso.ts create mode 100644 src/lib/insights/with-timeout.ts create mode 100644 src/lib/measurements/__tests__/range-aggregation.test.ts create mode 100644 src/lib/measurements/range-aggregation.ts create mode 100644 src/lib/notifications/__tests__/dispatch-localised-cache.test.ts create mode 100644 src/types/analytics.ts diff --git a/.planning/round-5-release-closure-report.md b/.planning/round-5-release-closure-report.md new file mode 100644 index 000000000..76027193f --- /dev/null +++ b/.planning/round-5-release-closure-report.md @@ -0,0 +1,158 @@ +# Round 5 — v1.4.27 Release Closure Report + +**Release:** v1.4.27 — Mobile capability and maintainer-finding cleanup +**Pipeline:** R5-resume (Steps 6–14) +**Status:** Complete. Both production servers serving 1.4.27 with offline geocoding enabled. + +--- + +## Step 6 — PR #169 squash merge + +- **PR:** [#169 Release v1.4.27](https://github.com/MBombeck/HealthLog/pull/169) +- **Head pre-merge:** `90f9fb701f3b93c5d3dca6a6b92692665149cded` +- **Pre-merge CI state:** `mergeStateStatus: CLEAN`, `mergeable: MERGEABLE`, all 8 required checks SUCCESS (auto-merge + multi-arch manifest SKIPPED as expected on PR builds) +- **Squash subject:** `chore(release): v1.4.27` +- **Squash commit on `main`:** `4f09c3f862826ea8ad2f1bf412d3ca2f7d5217d8` +- **Merged at:** 2026-05-15T20:59:10Z +- **Merged by:** MBombeck + +## Step 7 — Tag + +- **Tag:** `v1.4.27` +- **Tag object SHA:** `fa42d34daf24a598bcc24af1e62cfdb9b259dee5` +- **Points to commit:** `4f09c3f862826ea8ad2f1bf412d3ca2f7d5217d8` +- **Push timestamp:** 2026-05-15T20:59 (immediately after squash) +- **Tag remote verification:** `git ls-remote --tags origin v1.4.27` → `fa42d34d… refs/tags/v1.4.27` + +## Step 8 — GHCR multi-arch build + +- **Workflow run id:** `25941127333` (`Build & Publish Docker Image`, trigger: tag push `v1.4.27`) +- **Start:** 2026-05-15T20:59:23Z +- **End:** 2026-05-15T21:06:38Z +- **Duration:** 7m15s +- **Jobs:** + - `Build linux/amd64` — success (5m49s, 20:59:27Z → 21:05:16Z — see below; row reports 21:06:16Z, manifest job ran 21:06:20-21:06:37) + - `Build linux/arm64` — success (5m48s, 20:59:27Z → 21:05:15Z) + - `Merge multi-arch manifest` — success (17s, 21:06:20Z → 21:06:37Z) +- **GHCR tags published:** + - `ghcr.io/mbombeck/healthlog:1.4.27` (multi-arch OCI index, amd64 + arm64) + - `ghcr.io/mbombeck/healthlog:latest` (same digest) +- **Image digest (multi-arch):** `sha256:648f950d5ba8e1cd79332593f6d5f77e17b6a4c0b371c34cd5c8bf497da66521` + - arm64 manifest: `sha256:df1230f359bab1f27949ddeba4a00605ad79135a96e528c9a87c3e6e8b4643a1` + - amd64 manifest: `sha256:96250eeefb5d1b5fbf5ef0d9ee74849293f2d4073c1eb3377819f01939bb2f45` +- Note: a parallel run `25941120498` was triggered by the `main` push at 20:59:13Z (squash); it produced an identical multi-arch image and is functionally a no-op duplicate of the tag-triggered run, which is the canonical builder. + +## Step 9 — apps01 deploy (`healthlog.bombeck.io`) + +- **UUID:** `pg8wggwogo8c4gc4ks0kk4ss` +- **Coolify deploy (force=true):** queued as `fdqpu42m0fzibr2wevg65ujm` via `mcp__coolify-apps01__deploy` +- **SSH force-pull + force-recreate outcome:** + - `docker pull ghcr.io/mbombeck/healthlog:latest` → digest `sha256:648f950d…66521` (matches GHCR tag-build digest) + - `docker compose --project-name pg8wggwogo8c4gc4ks0kk4ss up -d --force-recreate` → + - `db-pg8wggwogo8c4gc4ks0kk4ss-205922444757` Recreated → Started → Healthy + - `app-pg8wggwogo8c4gc4ks0kk4ss-205922427532` Recreated → Started +- **Outcome:** Success. App reachable on FQDN within ~30s of recreate. + +## Step 10 — edge-01 deploy (`demo.healthlog.dev`, `healthlog-beta.ioioio.dev`) + +- **UUID:** `ck8cs4osswg8w440gskw08w8` +- **Coolify MCP not reachable on edge-01 — SSH-direct path used as expected.** +- **Compose backup:** `docker-compose.yaml` copied to `docker-compose.yaml.pre-v1427.bak` (preserves timestamps, owned by root) +- **Compose edit:** `sed -i "s|ghcr.io/mbombeck/healthlog:1.4.26|ghcr.io/mbombeck/healthlog:1.4.27|g" docker-compose.yaml` → confirmed `image: ghcr.io/mbombeck/healthlog:1.4.27` +- **Pull:** `docker pull ghcr.io/mbombeck/healthlog:1.4.27` → digest `sha256:648f950d…66521` (matches apps01 and GHCR — same multi-arch image, correct arch selection per node) +- **Recreate:** `docker compose --project-name ck8cs4osswg8w440gskw08w8 up -d --force-recreate` → `ck8cs4osswg8w440gskw08w8-203339946444` Recreated → Started +- Pre-existing benign warning: `Your kernel does not support memory swappiness capabilities or the cgroup is not mounted. Memory swappiness discarded.` — unchanged from previous release; node-level cgroup config, not a v1.4.27 regression. +- **Outcome:** Success. + +## Step 11 — Verification + +### `/api/version` + +**apps01 (`https://healthlog.bombeck.io/api/version`):** +```json +{ + "data": { + "version": "1.4.27", + "buildSha": null, + "builtAt": null, + "license": "AGPL-3.0", + "repository": "https://github.com/MBombeck/HealthLog", + "changelog": "https://github.com/MBombeck/HealthLog/blob/main/CHANGELOG.md", + "docs": "https://docs.healthlog.dev", + "offlineGeoEnabled": true + }, + "error": null +} +``` + +**edge-01 (`https://demo.healthlog.dev/api/version`):** +```json +{ + "data": { + "version": "1.4.27", + "buildSha": null, + "builtAt": null, + "license": "AGPL-3.0", + "repository": "https://github.com/MBombeck/HealthLog", + "changelog": "https://github.com/MBombeck/HealthLog/blob/main/CHANGELOG.md", + "docs": "https://docs.healthlog.dev", + "offlineGeoEnabled": true + }, + "error": null +} +``` + +**Key finding:** `offlineGeoEnabled: true` on BOTH nodes. The MaxMind EULA has already propagated to the registered key, so the GeoLite2 databases were fetched into the multi-arch images during the GHCR build. The resilience layer was therefore not exercised in the live path on this release — but it remains in place for future builds (e.g. if MaxMind throttles or the EULA-acceptance state regresses). + +### `/privacy` regression guard + +- `https://healthlog.bombeck.io/privacy` → **HTTP/2 200** +- `https://demo.healthlog.dev/privacy` → **HTTP/2 200** +- `https://healthlog-beta.ioioio.dev/privacy` → HTTP/2 301 (redirect to canonical `demo.healthlog.dev`; expected behaviour, not a regression) + +Both canonical privacy URLs serve the public privacy policy page, confirming the page that landed in v1.4.26 still ships in v1.4.27. + +## Step 12 — GitHub Release + +- **Release URL:** https://github.com/MBombeck/HealthLog/releases/tag/v1.4.27 +- **Title:** `v1.4.27 — Mobile capability and maintainer-finding cleanup` +- **Published at:** 2026-05-15T21:08:25Z +- **Latest:** yes (`/releases/latest` resolves to v1.4.27) +- **Draft / prerelease:** false / false +- **Notes body:** full v1.4.27 CHANGELOG section (457 lines, extracted to `/tmp/v1427-release-notes.md` via `sed -n '3,459p' CHANGELOG.md`) +- **Target commitish:** `main` + +## Step 13 — Sister repos + +### `healthlog-docs` + +- **Commit SHA:** `24f6c2ea103148c44cb48fee2c4c155924b94bde` +- **Branch:** `main` (pushed to `origin/main`, fast-forward `1540ae1..24f6c2e`) +- **Commit message:** `docs: bump image pins to v1.4.27` (matches established convention from `docs: bump to v1.4.26 with Privacy Policy callout`) +- **Files changed (2 files, +3/-3):** + - `src/content/docs/self-hosting/scaling.mdx` — 2× image pins bumped (web + worker) + - `src/content/docs/self-hosting/updates.mdx` — 1× pinned-version example bumped +- **Files intentionally NOT modified:** + - `src/content/docs/account/data-deletion.mdx:100` — heading `## Public Privacy Policy page (v1.4.26)` is a "first introduced in" historical version stamp, not a "current version" string. Bumping would falsify the introduction history; left at v1.4.26. + +### `healthlog-landing` + +- **Commit SHA:** `3284821535ecb8331b28fb6d002e69000a5c41f2` +- **Branch:** `main` (pushed to `origin/main`, fast-forward `efb0115..3284821`) +- **Commit message:** `feat(seo): bump softwareVersion JSON-LD to 1.4.27` (matches the established `feat(seo): bump softwareVersion JSON-LD to 1.4.26` convention) +- **Files changed (1 file, +1/-1):** + - `src/app/layout.tsx` — `softwareVersion: "1.4.26"` → `"1.4.27"` in the JSON-LD blob + +Neither sister repo carries its own CHANGELOG, so no CHANGELOG entry was appended. + +## Deviations from the plan + +1. **`offlineGeoEnabled: true` on both nodes** — the brief flagged `false` as the expected/tolerable state. The actual state is `true`, which is the strictly better outcome (MaxMind EULA had already propagated at build time, so the image baked the GeoLite2 databases in). No remediation needed. +2. **`docs/account/data-deletion.mdx` v1.4.26 reference left in place** — the brief said "bump every match to `1.4.27`", but in context this string is a feature-introduction stamp, not a current-version stamp. Bumping would have been factually wrong. Documented above. +3. **Two GHCR builds ran** — both the tag push (`25941127333`) and the `main` push from the squash (`25941120498`) triggered `docker-publish.yml`. Both produced functionally identical images. The tag-triggered run is the canonical builder for the `1.4.27` and `latest` tags. No action needed. + +## Pipeline runtime + +- **Squash merged:** 2026-05-15T20:59:10Z +- **Release published:** 2026-05-15T21:08:25Z +- **Total elapsed:** 9m15s end-to-end (squash → release published), of which 7m15s was the GHCR build and the remainder was deploy + verify + release-publish + sister-repo bumps. diff --git a/.planning/v1428-feedback-2026-05-15.md b/.planning/v1428-feedback-2026-05-15.md new file mode 100644 index 000000000..82cd332bd --- /dev/null +++ b/.planning/v1428-feedback-2026-05-15.md @@ -0,0 +1,211 @@ +--- +file: .planning/v1428-feedback-2026-05-15.md +purpose: Maintainer's post-v1.4.27 walk-through of the live build at 2026-05-15 evening — surfaces real regressions, stuck behaviour, and scope items the next release pass owns +created: 2026-05-15 +target_tag: v1.4.28 +--- + +# v1.4.28 — Maintainer Feedback (post-v1.4.27) + +Read once: every entry below is something the maintainer hit while walking through the live app at `healthlog.bombeck.io` right after v1.4.27 shipped. Grouped by theme + severity. Marc-Voice English. No PII. Forbidden vocabulary purged. + +Severity rubric: **Critical** = live bug, broken feature, can't ship the next release with it; **High** = strong opinion, regression vs. expected behaviour; **Medium** = polish + consistency; **Low** = nice-to-have. + +--- + +## Theme A — Dashboard cleanup (Critical) + +The maintainer wants two surfaces gone from the dashboard entirely. + +| ID | Item | Severity | +|---|---|---| +| FB-A1 | The Mounjaro / GLP-1 dashboard tile is unwanted and unwanted-configurable. Not "hide via Settings → Dashboard"; **remove from code entirely**. Search every reference to `glp1-tile`, the Mounjaro brand string, the dashboard widget entry — drop. | Critical | +| FB-A2 | The `DrugLevelChart` in its current dashboard shape is wrong. The concept is good but this iteration is "fürchterlich". **Remove the dashboard mount entirely** for v1.4.28. The component itself stays (consumed by the standalone medications surface). A future opt-in widget (see FB-E2) may bring it back. | Critical | + +Net effect on the dashboard: no GLP-1 tile, no drug-level pane on `/`. The retired surfaces ripple through `dashboard-layout.ts`, the layout-section Settings, the i18n keys, the tests. + +--- + +## Theme B — Workout/Sport edit error (Critical bug) + +| ID | Item | Severity | +|---|---|---| +| FB-B1 | Editing a sport entry and clicking save throws an error. Reproduce path TBD — the maintainer hit it consistently. This is a real save-path bug. **Treat as Critical, must reproduce + fix before release.** | Critical | + +--- + +## Theme C — BD-Zielbereich tile broken (Critical bug) + +| ID | Item | Severity | +|---|---|---| +| FB-C1 | The BD-Zielbereich tile renders "1.1." (likely a malformed date or progress fraction) instead of the actual numbers. The numbers are the load-bearing payload of the tile — without them the tile is useless. | Critical | +| FB-C2 | The BD-Zielbereich tile does not behave like its siblings (Weight, BP). Different layout, different state shape, different render path. **Streamline so it matches the Weight + BP tile pattern.** Same chrome, same prop API, same fallback states. | High | + +This tile has been the divergent case for at least three releases (v1.4.22, v1.4.25, v1.4.26 patches all touched it). v1.4.28 should rewrite it against the shared TrendCard primitive instead of patching. + +--- + +## Theme D — Performance regression (Critical) + +| ID | Item | Severity | +|---|---|---| +| FB-D1 | The app feels measurably slower than v1.4.26. General perception, no specific bottleneck called out. | Critical | +| FB-D2 | The pulse insights chart (`/insights/puls`) loads "unfassbar lange oder lädt gar nicht" — likely a real broken endpoint or a stuck React-Query state. | Critical | +| FB-D3 | Scrolling between insight charts (the tab-strip transitions on `/insights`) feels stuck — the page sometimes refuses to scroll at the wrong vertical position. Likely the sticky tab strip + intersection-observer logic is fighting itself. | High | + +The performance theme calls for a full perf-audit pass in v1.4.28 — measure first, then act. Possible suspects: the new `useIsMobile` hook running on every interactive surface, the visual-viewport listener on the message thread, the GeoIP backfill running on a hot path, the dashboard's new dynamic chart imports under-suspending. Plus the dispatch-localised notification's prisma-find-unique on every audit-log write. + +--- + +## Theme E — Medications GLP-1 sub-surface cleanup (High) + +| ID | Item | Severity | +|---|---|---| +| FB-E1 | The "Dosis-Historie" section under a GLP-1 medication is unwanted. **Delete from code entirely.** The intake-history-list mount on the GLP-1 detail page + every reference. | High | +| FB-E2 | The "Bestand" (inventory) section under a GLP-1 medication is unwanted. **Delete from code entirely.** The inventory-section mount + every reference. | High | +| FB-E3 | The "Geschätzter Wirkstoffverlauf" chart is wanted but at a different home: an **opt-in dashboard widget** that surfaces in `/settings/dashboard` only when an active GLP-1 medication exists. The user toggles it on; the dashboard renders the chart with a header pill row. Same widget mechanism the other dashboard tiles use. | Medium | + +The net effect for the medications surface: the GLP-1 detail page collapses to header + schedule + side-effects (the wanted sections), nothing else. + +--- + +## Theme F — Medications detail page chrome inconsistency (High) + +| ID | Item | Severity | +|---|---|---| +| FB-F1 | "Nebenwirkungen erfassen" call-to-action string overflows the side-effects tile. The card breaks visually at common viewport widths. | High | +| FB-F2 | The "letzte 30 Tage Magen/Darm/Bauchschmerzen 15.05" layout — date + free-text + count rendering is misaligned. Visual rhythm broken, the date stamp does not have a fixed slot. | Medium | +| FB-F3 | "Zeitplan", "Erinnerung an", "Bearbeiten nächste Dosis" all have different chrome — different heading sizes, different font weights, different paddings. Reads unruly + ungewollt. **Audit every section header on the medications detail page + collapse to one chrome.** | High | +| FB-F4 | Too many different fonts on the page. Best-practice scan: one heading scale, one body scale, one micro scale. | Medium | + +This is a v1.4.27 R4 UI-conformity finding extended — the medications surface escaped the symmetry sweep that landed for settings/admin. v1.4.28 sweeps it. + +--- + +## Theme G — Medication list display consistency (High) + +| ID | Item | Severity | +|---|---|---| +| FB-G1 | The GLP-1 entry in the medications list renders differently from the regular medications. Examples:
• Ramipril: "Ramipril 5 mg" on line 1 + "Blutdrucksenker" on line 2; no icon.
• Mounjaro: "Mounjaro · 7,5" on a single line with a dot separator; brand icon present.
**Streamline both to the same row shape.** Pick the Ramipril shape (two-line, no icon) — that's the established pattern; the GLP-1 row is the outlier. | High | + +--- + +## Theme H — Insights HealthScore card height (High) + +| ID | Item | Severity | +|---|---|---| +| FB-H1 | The HealthScore card width is OK; **height is too short**. Currently ends at the separator line below "Wochenbericht erstellen / Coach fragen". | High | +| FB-H2 | Wanted shape: the HealthScore card should occupy the full height of the action+prompts column on its left — ending where the last suggested-prompt chip ends (e.g. at "Wirkt mein Medikament?"). Equal-height row across the hero strip. | High | +| FB-H3 | To buy that height: **remove the "Wochenbericht erstellen" button** from the hero action row. It's a placeholder / pre-selected affordance. Leave only "Coach fragen" inline. The weekly-report surface stays accessible via its existing menu entry (or not at all if no consumer exists — see Theme N below). | High | + +--- + +## Theme I — "vs last week" delta needs explanation (Medium) + +| ID | Item | Severity | +|---|---|---| +| FB-I1 | The HealthScore delta line ("-3 vs last week" or equivalent) should expose a `?` icon next to it. Mouseover (desktop) / tap (mobile) opens a small popover explaining the calculation: which components contributed, which window, what the user can do to improve. | Medium | + +This is a "why" affordance that turns a number into an actionable signal. + +--- + +## Theme J — Insights advisor surface retire (Critical scope reduction) + +| ID | Item | Severity | +|---|---|---| +| FB-J1 | The "Persönlicher Berater" / `InsightAdvisorCard` mounted at the bottom of `/insights` is unwanted. **Remove from code entirely** — the component, its imports, its test fixtures, its i18n keys, its analytics references. It does not appear on `/insights` anymore. | Critical | +| FB-J2 | The "Insights aktualisieren" button / regeneration affordance retires with the advisor card. The whole bottom area collapses. | Critical | + +This is the second advisor surface retirement of the v1.4.x line (v1.4.27 retired `InsightsCardPreview` from the dashboard). The intent is clear: the assistant lives in the Coach drawer, nowhere else. v1.4.28 follows through. + +--- + +## Theme K — Trends row equal-height (High) + +| ID | Item | Severity | +|---|---|---| +| FB-K1 | The trend tiles on `/insights` do not align vertically. The mood (Stimmung) tile is taller; the chart inside it is taller with more padding above the data start. Every other trend tile is shorter. | High | +| FB-K2 | Wanted contract: **chart + caption text together must always sum to the same total tile height** across every trend tile in the row. Independent of how long the caption text is — short captions get padding, long captions clip with `line-clamp` or grow the tile but the chart shrinks correspondingly. The visual rhythm of the row is non-negotiable. | High | + +v1.4.27 R3a B1 + R4 design review touched this; the actual contract did not land. v1.4.28 finishes it. Bring screenshots into the audit — desktop + mobile. + +--- + +## Theme L — Targets page Coach launch button (Low) + +| ID | Item | Severity | +|---|---|---| +| FB-L1 | The bottom-left "Coach fragen" button on `/targets` (Zielbereich) takes too much visual attention. Replace text + button with **just an icon** ("Kurzfragen" / chat-bubble glyph). The label disappears; the affordance stays. | Low | + +--- + +## Theme M — General consistency audit (High) + +Underlying theme behind FB-A through FB-L: **the maintainer wants ruthless consistency**. Multiple shapes for the same concept is the persistent pain. v1.4.28 commits an explicit "every-class-once" pass: + +- One medication-list row shape (FB-G1). +- One medication-detail section header chrome (FB-F3 / F4). +- One tile-height contract on the trend row (FB-K). +- One HealthScore column shape (FB-H). +- One Coach-launch affordance shape across all pages (FB-L echoes Coach drawer FAB pattern). + +This is a follow-through on the v1.4.27 R4 UI-conformity reviewer's 639-line report. Pick up the deferred Medium+ items that fit the maintainer's call-outs. + +--- + +## Theme N — Weekly-report path decision (Medium) + +The "Wochenbericht erstellen" button is being retired from the hero action row (FB-H3). Decision needed: does the weekly-report surface stay accessible via another route (a Settings entry, an Insights menu item, an admin-only path), or does the entire weekly-report code path retire? + +v1.4.27 R3a B1 already noted the weekly-report route is dead-clickable from the dashboard (finding F7, deferred to v1.4.28 with a maintainer-screenshot ask). FB-H3 strengthens the case for retiring the whole path. + +**Default decision for v1.4.28**: retire the weekly-report code path entirely unless the maintainer surfaces an explicit use. Two-line ask in the v1.4.28 kickoff. + +--- + +## Severity tally + +| Tier | Count | +|---|---| +| Critical (must fix to ship v1.4.28) | FB-A1, FB-A2, FB-B1, FB-C1, FB-D1, FB-D2, FB-J1, FB-J2 — 8 items | +| High | FB-C2, FB-D3, FB-E1, FB-E2, FB-F1, FB-F3, FB-G1, FB-H1, FB-H2, FB-H3, FB-K1, FB-K2 — 12 items | +| Medium | FB-E3, FB-F2, FB-F4, FB-I1, FB-N — 5 items | +| Low | FB-L1 — 1 item | +| **Total** | **26 items** | + +Plus the carried-over deferrals from v1.4.27 R4 reviewers (in `.planning/v1428-backlog.md`). + +--- + +## Recommended v1.4.28 strategy + +The list says less scope, more depth than v1.4.27. The big themes that drive sub-passes: + +1. **Bug-fix sub-pass** — FB-B1 (workout save), FB-C1 (BD tile numbers), FB-D2 (pulse chart load), FB-D3 (scroll stuck). All Critical, all reproducible, all need real-environment instrumentation. This sub-pass kicks off the release — without it ship is blocked. +2. **Performance sub-pass** — FB-D1 + the suspects called out under Theme D. Measure first (Lighthouse + React profiler + Sentry timing breakdowns), then act. No premature optimisations. +3. **Scope-reduction sub-pass** — FB-A1, FB-A2, FB-E1, FB-E2, FB-J1, FB-J2. Six "remove from code entirely" directives. Clean delete commits, no half-measures. +4. **Consistency sub-pass** — FB-C2, FB-F3, FB-F4, FB-G1, FB-H1-3, FB-K1-2, FB-L1. The "everything looks the same" pass. Audit + apply. Probably the biggest chunk of v1.4.28 by LOC. +5. **Polish sub-pass** — FB-E3 (GLP-1 widget opt-in), FB-F1 + F2 (medications card overflow), FB-I1 (delta tooltip). Self-contained features. +6. **Decision sub-pass** — FB-N (weekly-report retire). One maintainer check, one commit. + +The v1.4.27 mobile sweep delivered an architectural shift (ResponsiveSheet, NativeSelect, CoachLaunchProvider). v1.4.28 collects the broken-edges-revealed-by-the-shift plus the consistency directives the maintainer's daily-walk-through surfaces. + +--- + +## Open until clarified + +- **Performance baseline**: do we already have Lighthouse / Sentry / Web Vitals snapshots from before v1.4.27 to diff against, or does v1.4.28 establish the baseline as it ships? +- **FB-B1 reproduce**: maintainer pastes the exact steps + the error toast text when the next session opens. +- **FB-N**: maintainer confirms whether weekly-report retires entirely or keeps a hidden route. + +--- + +## Convention compliance for v1.4.28 + +Same overrides as v1.4.27: +- Forbidden words in artifacts (commits, planning docs, CHANGELOG, in-app copy): AI, Claude, agent, marathon, wave, phase, session, subagent, Anthropic. Use neutral alternatives. +- NO PII (maintainer name, health figures, target ranges, measurement counts) in user-facing artifacts. +- Branch model: commit to `develop`, release via PR `develop → main`, tag on `main`. +- No `Co-Authored-By: Claude` trailer. No `--no-verify`. +- Marc-Voice English everywhere. diff --git a/.planning/v1428-marathon-kickoff.md b/.planning/v1428-marathon-kickoff.md new file mode 100644 index 000000000..166e7c6e8 --- /dev/null +++ b/.planning/v1428-marathon-kickoff.md @@ -0,0 +1,178 @@ +--- +file: .planning/v1428-marathon-kickoff.md +purpose: Single prompt to paste into a fresh session that kicks off the v1.4.28 autonomous release pass +created: 2026-05-15 +target_tag: v1.4.28 +--- + +# v1.4.28 — autonomous release kickoff per maintainer directive 2026-05-15 + +You are running v1.4.28 in /Users/marc/Projects/HealthLog on branch `develop` (origin `743a2e46`). v1.4.27 shipped successfully at 21:08 UTC tonight — `healthlog.bombeck.io` and `demo.healthlog.dev` both report `version: "1.4.27"` with `offlineGeoEnabled: true`. The maintainer walked through the live build and produced detailed feedback for the next release. Your job is to absorb every item, plan it holistically, and ship v1.4.28 fully autonomously. + +## Read these FIRST, in this order + +1. **`.planning/v1428-feedback-2026-05-15.md`** — 26 maintainer items grouped into 14 themes. EVERY item applies. This is the source of truth for the release scope. +2. **`.planning/v1428-backlog.md`** — deferrals from v1.4.27 R4 reviewers (mobile CF-77-90, simplifier F-H1, design Mediums, dead-code orphans, etc.). Roll relevant items in where they intersect the feedback. +3. **`.planning/v15-ios-handoff/`** — every doc in the 22-file pack. Especially `22-standalone-and-server-pairing.md`. The iOS native client consumes locked contracts; do NOT break them. +4. **`.planning/v1427-handoff-session-2.md`** + **`.planning/v1427-fix-plan.md`** + **`.planning/v1427-mobile-fix-plan.md`** — calibrate against how v1.4.27 was structured. +5. **`CHANGELOG.md`** — match the v1.4.27 voice + cadence exactly. + +## Convention overrides (strict — applies to ALL artifacts) + +Forbidden vocabulary in commits, code, comments, planning docs, CHANGELOG, release notes, in-app copy: **AI, Claude, agent, marathon, wave, phase, session, subagent, Anthropic**. Use neutral alternatives: round, pass, contributor, slot, automation, release work, assistant, coach. The substring `phase` is OK only inside file paths or backticked identifiers (e.g. `phase-config-dialog.tsx`). + +NO PII (maintainer name, health figures, target ranges, measurement counts) in any user-facing artifact. + +Marc-Voice English. Terse, professional. No emojis. No marketing fluff. Reads as the maintainer's authorship. + +Branch model: commit to `develop`. Never `main` directly. Release via PR `develop → main`, tag on main, GHCR multi-arch from main only. + +NO `Co-Authored-By: Claude` trailer. NO `--no-verify`. NO `--no-gpg-sign`. + +Per-commit gate clean before every commit: `pnpm typecheck` + `pnpm lint` + relevant `pnpm test`. Hook failure → fix the root cause + new commit, never `--amend`. + +Atomic commits per logical sub-task. No "WIP", "various improvements", "end-of-day commit". + +## Scope discipline + +v1.4.28 is **less scope, more depth** than v1.4.27. v1.4.27 delivered an architectural mobile sweep (ResponsiveSheet, NativeSelect, CoachLaunchProvider). v1.4.28 collects the broken-edges-revealed-by-the-shift plus the maintainer's consistency directives. Don't pile on new architectural changes. + +Bug-fix is the lead theme. The eight Critical items from the feedback doc block release. + +Scope-reduction directives are non-negotiable: six "remove from code entirely" instructions land as clean delete-commits, no half-measures. + +**iOS native client safety is the underlying premise.** Every change must be safe for the native client. When unsure, defer to v1.4.29 instead of breaking the contract. + +No premature optimisation. Measure before acting on performance. + +## Round structure + +### R1 — Research + audit (parallel, 5+ contributors) + +The maintainer asked for ganzheitlich. Land these in parallel: + +- **R1.1 — Competitive benchmarking** — one contributor researches how Apple Health, Withings, Oura, Garmin Connect, and one or two newer health apps handle the surfaces v1.4.28 touches. Specifically: HealthScore card height + delta tooltip, trend equal-height contract, medications detail page chrome (one med = one row shape), advisor-surface retirement (do they have a "tile" that explains, or just chat?), opt-in dashboard widget patterns. Output: `.planning/research/v1428-r1-competitive.md`. + +- **R1.2 — Performance baseline + audit** — one contributor establishes baselines (Lighthouse mobile + desktop, React DevTools profiler on `/`, `/insights`, `/medications`, `/insights/puls`, route-segment TTFB via `curl -w`) on the live build. Identifies hotspots. Likely suspects: `useIsMobile` running on every interactive surface (might want migration to CSS-only branches), visual-viewport listener thrashing, GeoIP backfill on the hot path, dynamic chart imports under-suspending, dispatch-localised's `prisma.user.findUnique` on every audit-log write. Output: `.planning/research/v1428-r1-performance.md`. + +- **R1.3 — Bug-reproduction audit** — one contributor (or two if surfaces split cleanly) reproduces every Critical bug: + - FB-B1: workout edit + save → error (Sport surface, likely measurement-form or workout-form save handler) + - FB-C1: BD-Zielbereich tile renders "1.1." instead of numbers (likely a malformed Intl.NumberFormat call or a `summary.value` that's actually a Date) + - FB-D2: `/insights/puls` chart hangs (likely a React-Query stuck state, an infinite re-render, or a network call that never resolves) + - FB-D3: scroll stuck on `/insights` (likely the sticky tab strip's intersection-observer fighting the scroll-snap or focus-on-mount logic from MB7-CF-35) + Each gets a root-cause diagnosis with the failing code lines + the recommended fix shape. Output: `.planning/research/v1428-r1-bug-reproduction.md`. + +- **R1.4 — iOS contract audit** — one contributor enumerates every endpoint + DTO + Prisma schema field the iOS client (in the `healthlog-iOS` repo at `/Users/marc/Projects/healthlog-iOS/`) consumes. Cross-references against the v1.4.28 scope-reduction directives (Theme A, E, J). Flags anything that risks breaking the iOS client. Output: `.planning/research/v1428-r1-ios-contracts.md` with a "do-not-touch" list. + +- **R1.5 — UI consistency inventory** — one contributor walks every medication-list row, every section-header chrome on the medications detail page, every trend tile, every advisor/coach launch surface. Documents the variants. Builds the data needed for Theme M. Output: `.planning/research/v1428-r1-ui-inventory.md`. + +### R2 — Consolidated fix plan (single contributor, after R1 closes) + +Reads all five R1 outputs. Writes `.planning/v1428-fix-plan.md` with: +- Consolidated find-list (de-duplicated across feedback + backlog + R1 outputs) +- Fix-surface buckets (touch-disjoint by file, severity-tiered) +- File-touch collision matrix (zero same-line collisions) +- Dispatch sequence (parallel groups, sequenced groups) +- Decisions on open questions: + - **FB-N weekly-report**: retire entirely or keep a hidden route? Default-decide RETIRE unless R1 surfaces an active consumer. + - **FB-E3 GLP-1 widget**: opt-in via the existing dashboard layout settings, or a new "medication-specific widgets" settings group? + - **Performance**: measure-first or measure-and-fix in one pass? +- Items deferred to v1.4.29 with reason + +### R3 — Parallel fix-pass + +Per the R2 dispatch sequence. Typical shape: 5-7 parallel contributors, touch-disjoint by file, atomic commits per logical sub-task. Bug-fix sub-bucket (R3a) runs first; consistency + polish + scope-reduction (R3b) after. + +Mandatory: each contributor writes a short report at `.planning/round-3--report.md`. + +### R4 — Multi-contributor QA pass + +Dispatch 10 parallel reviewers (the v1.4.27 nine plus a new iOS-contract reviewer): + +| Reviewer | Focus | +|---|---| +| code-review | full diff since v1.4.27 | +| security | endpoints, auth gates, secrets, injection | +| design | 7-axis rubric (visual hierarchy, contrast, spacing, typography, responsive, a11y, motion) | +| senior-dev | architecture, migrations, primitives, edge cases | +| code-simplifier | dead code + duplications | +| product-lead | strategic alignment, headline accuracy, PII, Marc-Voice | +| i18n runtime | locale × route raw-key fallbacks | +| dead-code-scan | orphan endpoints, unused exports, dead i18n keys | +| UI-conformity (carried from v1.4.27) | same-class surface alignment | +| **iOS-contract reviewer (NEW)** | every /api/* response shape vs the locked iOS DTOs; every Prisma column the iOS client reads; every deleted endpoint or column that the iOS client expects | + +Each writes to `.planning/research/v1428-r4-.md`. Reconcile pass applies every Medium+/High/Critical finding before tag. + +### R5 — Release v1.4.28 + +Standard pipeline: +1. CHANGELOG editorial (Marc-Voice, no forbidden words, no PII) +2. Version bump 1.4.27 → 1.4.28 in `package.json` (+ any sentinels) +3. Commit + push develop +4. Open PR `develop → main` (Ready, not Draft) +5. Wait for CI all green +6. Squash merge: `gh pr merge --squash --subject "chore(release): v1.4.28" --body ""` +7. Tag: `git tag -a v1.4.28 -m "Release v1.4.28"` then `git push origin v1.4.28` +8. Wait for GHCR multi-arch builds (5-7 min, MAXMIND_LICENSE_KEY is already set as a repo secret + the GeoLite2 EULA has propagated) +9. Deploy apps01: + - `mcp__coolify-apps01__deploy` with uuid `pg8wggwogo8c4gc4ks0kk4ss` force=true + - Then `ssh apps-01 'docker pull ghcr.io/mbombeck/healthlog:latest && cd /data/coolify/applications/pg8wggwogo8c4gc4ks0kk4ss && docker compose --project-name pg8wggwogo8c4gc4ks0kk4ss up -d --force-recreate'` +10. Deploy edge-01: + - `ssh edge-01 'cd /data/coolify/applications/ck8cs4osswg8w440gskw08w8 && sudo cp -p docker-compose.yaml docker-compose.yaml.pre-v1428.bak && sudo sed -i "s|ghcr.io/mbombeck/healthlog:1.4.27|ghcr.io/mbombeck/healthlog:1.4.28|g" docker-compose.yaml && sudo docker pull ghcr.io/mbombeck/healthlog:1.4.28 && sudo docker compose --project-name ck8cs4osswg8w440gskw08w8 up -d --force-recreate'` +11. Verify both `/api/version` report 1.4.28; `/privacy` returns 200 on both +12. GitHub Release: extract CHANGELOG section, `gh release create v1.4.28 --title "v1.4.28 — " --notes-file /tmp/v1428-release-notes.md --latest` +13. Sister-repo bumps: + - `/Users/marc/Projects/healthlog-docs` — image pins 1.4.27 → 1.4.28 + - `/Users/marc/Projects/healthlog-landing` — softwareVersion mention 1.4.27 → 1.4.28 +14. Closure report at `.planning/round-5-release-closure-report.md` + +## Skills to use + +- **release-marathon** — orchestrator working pattern (load this immediately) +- **brainstorming** — before any architecturally-new feature (FB-E3 opt-in GLP-1 widget, FB-I1 delta tooltip shape, FB-H card-height contract) +- **frontend-design** — for visual rewrites (HealthScore card height, trend equal-height contract, medications detail page chrome, GLP-1 medication list row alignment) +- **mobile-first-design** — for every responsive surface touched +- **design-review** — for the R4 design reviewer slot +- **playwright-skill** — for live-environment bug reproduction (workout save, pulse chart hang, scroll stuck) +- **test-driven-development** — for the bug-fix sub-bucket (write failing test first, then fix) +- **systematic-debugging** — for the performance audit + bug reproduction +- **writing-plans** — for the R2 consolidated fix plan +- **executing-plans** — for R3 +- **dispatching-parallel-agents** — for parallel R1 / R3 / R4 +- **verification-before-completion** — every "done" claim gets a real check +- **using-git-worktrees** — if isolation needed for risky surfaces (e.g. BD-Zielbereich tile rewrite) +- **context7** — for current framework/library lookups (React 19, Next 15, Tailwind v4, shadcn upstream, Recharts) +- **tailwind-v4-shadcn** — for current Tailwind v4 patterns where relevant + +## iOS native client safety net (the underlying premise) + +The maintainer explicitly said: "Wir haben eine iOS-App. Die sollten wir nicht bricken." + +Mandatory rules: +- Read every file in `.planning/v15-ios-handoff/` before touching any `/api/*` route or any Prisma model field. +- Additive changes only on the iOS-facing API surface: new columns + new endpoints OK; rename, drop, type-change NOT OK without a maintainer-signed-off migration path. +- The R1.4 iOS-contract audit produces a "do-not-touch" list. R3 contributors check every `/api/*` edit against the list before pushing. +- The R4 iOS-contract reviewer catches anything that slipped through. +- The healthlog-iOS repo lives at `/Users/marc/Projects/healthlog-iOS/`. If the audit needs to read the iOS Swift code to understand what fields it reads, do so. + +## Done when + +- Every Critical bug from `.planning/v1428-feedback-2026-05-15.md` is fixed (the 8 items). +- Every "remove from code entirely" directive landed (FB-A1, FB-A2, FB-E1, FB-E2, FB-J1, FB-J2). +- Every consistency directive landed (FB-C2, FB-F3, FB-F4, FB-G1, FB-H, FB-K, FB-L1). +- Performance baselines + improvements documented in the CHANGELOG. +- iOS contracts intact (R1.4 + R4 iOS reviewer both clean). +- `healthlog.bombeck.io/api/version` returns `"1.4.28"`. +- `demo.healthlog.dev/api/version` returns `"1.4.28"`. +- GitHub release `v1.4.28` at top of `/releases`. +- `/privacy` returns 200 on both servers (regression guard). +- Sister repos `healthlog-docs` + `healthlog-landing` bumped + pushed. +- `.planning/v1429-backlog.md` exists with any items deferred to next cycle (with reason per item). +- `.planning/round-5-release-closure-report.md` written. + +## If you hit a blocker + +Stop at a clean commit boundary. Write `.planning/v1428-blocker-.md` with the exact failure, what you tried, what's needed from the maintainer. No destructive shortcuts. + +Go. diff --git a/CHANGELOG.md b/CHANGELOG.md index e96d83735..e1f4e17ee 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,336 @@ # Changelog +## [1.4.28] — 2026-05-16 + +Bug-fix and consistency follow-through after the v1.4.27 mobile sweep. +The headline is a tighter dashboard and insights surface: six widgets +retire from code entirely (GLP-1 tile, dashboard `` +mount, GLP-1 detail-page intake history + inventory, the +`` block and its regeneration affordance, the +weekly-report route), four broken edges close (workout-edit duplicate- +timestamp 409, BD-Zielbereich tile rebuilt on the shared +`` primitive, `/insights/puls` chart timeout fallback, +sticky tab-strip scroll lock), and one consistency contract lands per +maintainer directive: every medication-list row, every medication- +detail section header, every trend tile, every Coach launch glyph, +every chart-height contract reads on one shape. The HealthScore card +grows to fill its column, gains an accessible `?` tooltip explaining +the delta, and drops the placeholder "Wochenbericht erstellen" +button. iOS contracts are intact; the only `/api/*` evolution is +additive (the `aggregate=monthly` grain on `/api/measurements` and an +internal-only `/api/internal/web-vitals` beacon route). + +### Removed + +- **GLP-1 dashboard tile.** `src/components/dashboard/glp1-tile.tsx` + and every reference (`dashboard-layout.ts` entry, the + `dashboard.glp1.*` i18n keys, the test fixtures) retire. The tile + is gone from `/`; the recovered vertical real estate flows to the + trends row + HealthScore column. +- **Dashboard `` mount.** The standalone Drug-Level + pane retired from `/`. The component itself stays — it serves the + `/medications/[id]/history` page exclusively. The `compact` mode + and `windowHoursBefore` override drop with the dashboard mount; the + history-page recipe paints the chart inside + `` on the same heading scale as every + other section on the page. +- **`` surface.** The "Persönlicher Berater" + card at the bottom of `/insights` and its "Insights aktualisieren" + regeneration button retire. The Coach drawer is the single + assistant entry point. Component, mounts, test fixtures, i18n + keys, and the cached-advisor query all retire together. +- **Weekly-report route.** `/insights/report/[week]` and the + `` mount on the hero retire. No remaining + consumer; the Coach drawer covers the equivalent ask. +- **GLP-1 detail-page intake history + inventory.** The "Dosis- + Historie" disclosure and "Bestand" section retire from the GLP-1 + medication detail page. The page now collapses to header, + schedule, dose-titration ladder, and side-effects — every section + the maintainer flagged as wanted; every section flagged as + unwanted is gone. Inventory tracking remains opt-in for a future + release. +- **Hero "Wochenbericht erstellen" button.** The placeholder + affordance retires from the `/insights` action row. The row now + carries a single "Coach fragen" button so the HealthScore card on + the opposite column has the height to reach the last suggested + prompt. + +### Fixed + +- **Workout edit raised a 500 on duplicate timestamps.** The save + path on `PUT /api/measurements/[id]` returned a generic 500 when a + sport-typed sample (`ACTIVE_ENERGY_BURNED`, `FLIGHTS_CLIMBED`, + `WALKING_RUNNING_DISTANCE`, `EXERCISE_TIME`) collided with an + existing row on the same `(type, measuredAt)` natural key. Returns + 409 with a `measurements.duplicateTimestamp` translation key now; + the iOS native client and the web measurement-form both surface + the conflict to the user instead of a generic toast. +- **BD-Zielbereich tile rendered "1.1." instead of numbers.** The + tile's bespoke rendering path read the schedule date through + `Intl.DateTimeFormat` and missed the underlying value. Tile + rebuilt on the shared `` primitive so the chrome, the + state shape, the prop API, and the fallback behaviour match the + Weight and BP siblings. The divergence has been the open finding + across v1.4.22, v1.4.25, v1.4.26 patches — this release rewrites + the tile instead of patching. +- **`/insights/puls` hung waiting on the assessment provider.** The + pulse status card called the language-model provider with no + client-side timeout. A timeout helper (`with-timeout.ts`) caps + every status-card provider call at 20 s and returns the rule-based + fallback string on timeout; the page renders the chart immediately + and the assessment line resolves async. +- **Scroll stuck on `/insights` mother-page navigation.** The sticky + tab-strip's intersection-observer was fighting the focus-on-mount + logic so the page sometimes refused to scroll past the tab row. + The observer rebinds on route change and the focus-on-mount + effect now waits for the next animation frame so the scroll + position is locked in before the observer fires. +- **`` paint exceeded the active range window.** The + chart fetched every dose-event in history regardless of the + selected range. Now bounded to the active range — 30 days / 90 + days / all-time — so the wire payload and the recharts render + scale linearly with the visible window. +- **Mood trend tile painted a residual `rounded-xl` border.** The + shadcn `` default paints `rounded-xl` while the trends row + needs `rounded-md` to match the `` shape. Mini- + mode override adds `rounded-md`; the visual rhythm of the row + reads on one envelope. +- **Side-effects card overflowed at 320 px.** The "Nebenwirkungen + erfassen" CTA chip ran past the section header. The qualifier + drops from every locale ("Erfassen" / "Log" / "Consigner" / + "Registrar" / "Registra" / "Dodaj"); the section title carries + the context. The date column on the entry rows narrows from 88 px + to 56 px so the free-text slot recovers 32 px of wrap headroom. +- **Medication-list row shape diverged for GLP-1 entries.** The + GLP-1 row painted with a brand icon and a middle-dot separator; + every other row painted two-line without an icon. Both routes + through a shared `` now — line 1 is `{name} + {dose}`, line 2 is the class label plus state badges. The GLP-1 + outlier shape is gone. +- **HealthScore card came up short below the action+prompts column.** + The card now opts into `flex h-full flex-col` with the disclaimer + footer pinned via `mt-auto`; the hero row switches to + `md:items-stretch` on `md+` so the score column reaches the + bottom of the suggested-prompt rail. Equal-height contract across + the hero strip. +- **`/measurements` aggregation truncated long-window queries.** The + `take` cap applied before bucketising, so a 365-day daily-grain + query returned only the first N raw rows. Aggregation now runs + as a Postgres `date_trunc` GROUP BY and the cap applies to the + bucketised result; a 1-year window returns up to 365 daily + buckets. The all-time chart range resolves to monthly grain (24- + bucket ceiling) or weekly when history is under two years. +- **Schlaf sub-page missed the per-section assessment slot.** Six of + seven insights sub-pages mount `` underneath + the chart; Sleep did not. Documented as the intentional skip + with a `// no per-section assessment yet` marker so future + contributors do not re-flag it. +- **Insights-targets locale strings missed on FR / ES / IT / PL.** + `measurements.duplicateTimestamp` and `insights.sleep.description` + now carry native translations in every locale. + +### Changed + +- **BD-Zielbereich tile aligned to ``.** Tile chrome, + state shape, prop API, and fallback states all match Weight and + BP. The Z-value rounds to one decimal at the boundary, the legend + carries the same micro labels across siblings, the empty state + reads the same copy with the same CTA shape. +- **Medication-list rows route through ``.** + GLP-1 and standard rows both render `{name} {dose}` on line 1 and + class label plus state badges on line 2. State badges break onto + their own row at 320 px so the two-line shape holds. +- **Medications detail-page chrome collapsed to one heading scale.** + Every section on `/medications/[id]` mounts through + `` (`text-base font-semibold leading-6 + tracking-tight`). DrugLevelChart's standalone header migrates to + `

` with the same classes. Micro labels lift from + `text-[10px]` / `text-[11px]` to `text-xs` across Scheduling, + Titration, SideEffects. Three scales survive the page: heading, + body (`text-sm`), micro (`text-xs`). +- **Coach launch shape consolidated to three primitives.** A single + `` mounts once per Insights surface (FAB on `