# Changelog ## [1.4.29.1] — 2026-05-16 — Daily-step aggregation hotfix A one-line follow-up to v1.4.29. The dashboard 7-day step chart now renders daily totals (sums in the thousands), not per-sample averages (hundreds), matching the server-side `aggregate=daily` contract that landed earlier today. ### Fixed - **Client-side daily aggregator.** `health-chart.tsx` reduced every HealthKit type with `sum / count` regardless of whether the metric is cumulative or spot. The branch now consults `CUMULATIVE_HK_TYPES` and picks `sum` for steps, active energy, flights climbed, walking + running distance, and time in daylight, while the spot metrics (BP, weight, pulse, BG, body fat, mood, sleep) keep the mean. ### Tests - The existing chart suite (137 specs) continues to pass; the one-line branch is exercised by the v1.4.29 server-side aggregation tests and needs no additional coverage. ## [1.4.29] — 2026-05-16 — Dashboard performance + chart polish A targeted performance + polish patch. The headline is a faster dashboard on data-rich accounts: the pulse chart drops up to ~5 000 raw rows per range tab in favour of one row per day, the duplicate `/api/dashboard/widgets` fetch on every mount collapses to one, and three long-running analytics reads are bounded to the trailing window the tile path actually needs. Two production regressions close — the `aggregate=daily|weekly|monthly` path on `/api/measurements` 500'd for every grain, and the same path averaged cumulative step counts instead of summing. Mobile chrome gets a tighter rhythm: dashboard tiles pin to a single 140-px contract at `` range test asserts the default 30-day window asks for `aggregate=daily` and the `windowOverride="last7days"` mini-chart does not. - `` mobile-tile-height contract pinned via a new snapshot test (`trend-card-tile-height.test.tsx`). ## [1.4.28.1] — 2026-05-16 — Dashboard-save hotfix "Speichern" on the Dashboard works again for every account whose layout was last persisted before v1.4.28. The v1.4.28 retire of the `glp1` widget id left an orphan entry in `dashboardWidgetsJson` for legacy users, which made the save validator reject the payload; the resolver now filters unknown widget ids on read. ### Fixed - **Dashboard save against legacy layouts.** `resolveDashboardLayout` in `src/lib/dashboard-layout.ts` drops widget ids that are no longer in the registry before returning the layout, so the `dashboardWidgetsJson` round-trip (`GET /api/me/dashboard` → user edit → `PUT /api/me/dashboard`) succeeds even when the persisted JSON still names `glp1` or any other retired widget. ### Tests - `src/lib/__tests__/dashboard-layout.test.ts` gains a case for the retired-id filter (19/19 green). ## [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 `