From 36bf778fd821f7e08850795d1596560ca7c5354e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andr=C3=A9=20Bombeck?= Date: Sat, 23 May 2026 10:43:04 +0200 Subject: [PATCH] =?UTF-8?q?chore(release):=20v1.4.48=20=E2=80=94=20backlog?= =?UTF-8?q?=20closure,=20perf=20cap,=20Coach=20+=20Withings=20+=20onboardi?= =?UTF-8?q?ng=20polish?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Ships the deferred Mediums and Lows from the v1.4.47 audit round. Greens up the workout-batch integration suite that had been red on main for three releases. Extends the v1.4.47.1 90-day outer-WHERE cap to the cold-mount /api/analytics fallback so the cold slim slice drops from multi-second to sub-second on tenants with large measurement partitions. Adds the admin notifications-diagnostic endpoint that the v1.4.47 APNs investigation needed but did not have. Same commit closes H1 — the v1.4.45 and v1.4.46 CHANGELOG sections are scrubbed of internal release-process taxonomy. Code comments in summaries-slice no longer reference specific tenant row counts; the attempt-number runbook references migration 0077 instead of the stale 0076. Deferred: - Server-side suppression of MEDICATION_REMINDER APNs for iOS v0.6.0.7+ clients (issue #206) — waits for iOS v0.6.0.8 to ship the notification-permission re-trigger fix (healthlog-iOS#10). - /api/admin/notifications/diagnostic OpenAPI entry — admin-only, internal consumer today; v1.4.49 lands the iOS-side companion. --- CHANGELOG.md | 165 ++++++++++++++++++--------- docs/ops/attempt-number-semantics.md | 2 +- package.json | 2 +- src/lib/analytics/summaries-slice.ts | 36 +++--- 4 files changed, 130 insertions(+), 75 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9c8d22234..20605eae0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,60 @@ # Changelog +## [1.4.48] — 2026-05-23 — Audit-backlog closure: integration suite green, perf cap on cold-fallback path, Withings + Coach + onboarding polish + +v1.4.47 closed the H1 + Mediums punch list. v1.4.48 is the dedicated low-priority sweep: the Mediums and Lows deferred from the v1.4.47 audit round land together, the workout-batch integration suite that had been red on `main` for three releases turns green, the cold-mount analytics fallback path picks up the same 90-day outer cap that v1.4.47.1 shipped for the rollup-fresh path, and the iOS validation-failure audit rows now carry the rejected payload shape so the iOS team can chase serialiser drift from a single log line. + +### Added + +- `/api/admin/notifications/diagnostic` — admin-only endpoint that surfaces device APNs token presence (masked to an 8-char prefix + 8-char suffix), notification channel config presence, and recent push attempts for the calling account. Closes the DB-shell bottleneck that slowed the v1.4.47 APNs investigation. +- Native sub-locale translations for `settings.about.tourReplay` and `tourReplayHint` (es / fr / it / pl). Tightened `dashboard.dragHandleHint` copy in de + pl (was ~30 words on a `sr-only` paragraph; screen readers spent ~6 s reading it on focus). +- OpenAPI documentation for `GET` + `PATCH /api/auth/me/disable-coach`. +- Operations runbook notes: `attemptNumber` audit-row vs alert-signal semantics at `docs/ops/attempt-number-semantics.md`, and explicit migration-before-container ordering at `docs/ops/deploy.md` for manual / partial-deploy scenarios. + +### Changed + +- `/api/analytics` cold-fallback path splits the heavy single-statement aggregate into two parallel queries. The linearly composable columns (`count` / `min` / `max` / `mean`) keep the full-partition scan; the windowed 7 / 30 / 90-day `avg` / `slope` / `r²` columns take a 90-day outer cap so the planner does an index range scan on `(user_id, type, measured_at)` instead of a full-partition sequential scan. Output is bit-identical; cold slim slice drops from multi-second to sub-second on tenants with large measurement partitions. +- Dashboard reorder + onboarding tour helpers consolidated. Arrow-button reorder delegates to the same swap path as drag-and-drop. `restartOnboardingTour` extracted so About + Account sections share the surface. `useSortable` honours `prefers-reduced-motion`. Drag-handle `aria-describedby` hint gates on visible widget count so the empty-state cannot orphan the hint paragraph. +- Coach disable surface tightened. The status banner auto-clears after 3 s. `PATCH /api/auth/me/disable-coach` body migrated to Zod + `returnAllZodIssues` for the uniform 422 envelope. The `eslint-disable react-hooks/rules-of-hooks` line in `useDisableCoach` is replaced with a shared `useQueryClientMounted` helper that both `useFeatureFlags` and `useDisableCoach` consume. +- Withings OAuth callback consumes the nonce atomically. `delete` is issued first; the `P2025` catch handles the replay branch. The previous read-then-delete pattern allowed a theoretical race between two concurrent callbacks with the same nonce. Silent `.catch(() => {})` on delete failures now surface via a Wide-Event warning so a real infra failure reaches the audit trail. +- Withings `connect` endpoint rate-limited to 10 calls per 60 s per user (mirroring the disable-coach pattern); row-create failures redirect to the consistent error surface (`/settings/integrations?withings=error&reason=connect`) instead of bubbling a 500. +- Worker boot emits a Wide-Event warning if any `integration_statuses` row still has a NULL per-kind counter after the v1.4.47 legacy-column drop. Closes the silent alert-ladder gap where such rows would alert two strikes later than they should. + +### Fixed + +- Workout-batch integration tests (`workout-batch-create`, `workout-batch-race`) updated to match the v1.4.42 cross-source dedup semantics. The integration suite had been red on `main` since v1.4.45 (three release commits shipped through it). Suite is green again. +- Twelve stale "migration 0076" references in code comments corrected. `consecutiveFailures` sites read 0077; `disable-coach` sites read 0078. Comment-only sweep, no behavioural change. +- `docs/ops/attempt-number-semantics.md` referenced migration 0076 for the dropped `consecutiveFailures` column; the actual migration is 0077. + +### Performance + +- Cold-fallback `/api/analytics` slim slice — `aggregates` query split so the windowed columns get a 90-day outer `WHERE` cap, matching the inner `FILTER` windows. Multi-second TTFB on cold mounts drops to sub-second on tenants with large measurement partitions. The `narrows` query received the same cap in v1.4.47.1. + +### Security + +- Withings `connect` rate-limited (per-user 10 / 60 s) to bound ledger-row growth from a misbehaving client. +- Coach `disable-coach` validation envelope unified through `returnAllZodIssues` for a uniform 422 shape. + +### Observability + +- `dashboard.widgets.validation-failed` and `measurements.series.validation-failed` audit rows now carry `received_keys`, a truncated `received_shape_excerpt` (256-char hard cap), and the sanitised Zod issues. One log line per validation failure carries both the iOS-sent shape and the server's rejection reason, so iOS serialiser drift can be chased without DB-shell access. + +### Documentation + +- `NEXT_PUBLIC_APP_VERSION`, `NEXT_PUBLIC_APP_BUILD_SHA`, `NEXT_PUBLIC_APP_BUILT_AT` documented as build-time-only env-vars in `docs/self-hosting/reverse-proxy.md`. The Coolify runtime entry was misleading; Next.js bakes the values into the bundle at build time via the `next.config.ts` `env:` block. + +### Test hygiene + +- New grep-discovery test mirrors the `flags.coach` discovery test for `user.disableCoach` / `useDisableCoach`. New sites must be added to the allowlist explicitly so CI fails on an unreviewed addition. +- `target-card.tsx` per-card "Ask the coach" CTA pinned in the user-disable fixture (the cascade fixture already covered the page-level gate). +- `textarea` `forwardRef` `Symbol.for("react.forward_ref")` sentinel assertion deleted (React-internal API). Ref forwarding is covered indirectly by the call sites that pass `ref` to `