chore(release): expand v1.4.25 with Wave 4-5 and reconcile features

Rewrites the v1.4.25 CHANGELOG section to cover the ~90 commits since
v1.4.24 across onboarding rebuild, GLP-1 medication tracking end-to-end
(drug knowledge, Research Mode with MDR acknowledgment dialog,
estimated drug-level chart, side-effect taxonomy, cadence + compliance,
EMA titration ladder, pen-and-vial inventory with a 30-day clock),
Personal Records detection worker + badge + push opt-in, workout batch
ingest, Withings Activity + Sleep v2 syncs, EMA drug knowledge layer,
log-redaction path-segment rule that closes the Withings webhook
secret leak, GLP-1 convenience endpoint hardening, native first-party
Coach + insights system prompts across six locales with a refusal-probe
matrix, OpenAPI drift-gate hard-fail flip, 380 dead i18n keys removed,
shared helpers + section wrapper consolidations, dashboard top-tile
polish, design polish to WCAG 2.5.5 + AA contrast.

Updates the test count footer to the current 3828 unit / 344 files /
~170 integration / 9 new migrations (0051-0059) + 1 hardening (0060).
Adds the refusal-probe matrix metrics (1800+ assertions x 15 GROUND
RULES x 6 locales x 20+ paraphrasings).

Replaces the stale "Deferred to v1.4.26" section. Items that already
shipped in Wave 4-5 (OpenAPI hard-flip, onboarding rebuild, native
FR/ES/IT/PL Coach prompts, PR detection worker, workout ingest,
Withings Activity + Sleep v2) are out of the deferred list and into
Added / Changed / Fixed / Security / Refactor. The remaining v1.4.26
backlog reads accurately.
This commit is contained in:
Marc-André Bombeck committed 2026-05-14 23:19:26 +02:00
1 parent 068b49e709
commit 49f71c9215
1 file changed
+467 -95
+467 -95
View File
@@ -4,18 +4,29 @@
Largest feature delta in the v1.4.x line. Insights expands from one
page into seven dedicated metric routes; GLP-1 medication tracking
lands end-to-end from injection picker through dashboard tile, weight-
chart markers, therapy timeline, plateau detection and doctor-report
section; cross-source priority becomes a two-axis resolver with a
per-user Settings surface; per-user timezone threads through ten
analytics and presentation surfaces; Withings coverage doubles with
twelve new measurement types and webhook-driven BP/temperature
ingestion; Health-Score gains a per-component provenance accordion;
four AI-translated locales (FR / ES / IT / PL) ship behind a
maintainership banner; the GHCR image is now multi-arch so Apple
Silicon Macs and arm64 clouds pull native. Migrations 0044–0054 are
all additive and forward-only. `PROMPT_VERSION` 4.24.0 → 4.25.0 with
GROUND RULE 9 (Coach refuses GLP-1 dose recommendations).
lands end-to-end across injection picker, dashboard tile, weight-chart
markers, therapy timeline, plateau detection, drug-level chart with a
Research-Mode acknowledgment gate, EMA-sourced drug knowledge, EMA
titration ladder, cadence visualisation, compliance chips, side-effect
taxonomy, pen-and-vial inventory with a 30-day in-use clock, and a
dedicated doctor-report section; cross-source priority becomes a
two-axis resolver with a per-user Settings surface; per-user timezone
threads through ten analytics and presentation surfaces; Withings
coverage doubles with twelve new measurement types, webhook-driven
BP / temperature ingestion, plus Activity (steps + distance +
active-energy) and Sleep v2 (stage-level segments) syncs; the
onboarding flow is rebuilt as a nested-route wizard with welcome
carousel, goals chip-picker, source selection grid, baseline form,
and a welcome-back resume banner; Personal Records ship end-to-end
with a detection worker, push opt-in, and a metric-trend badge;
Health Score gains a per-component provenance accordion; the Coach
runs on native first-party prompts across all six locales with a
1800-assertion refusal-probe matrix; the OpenAPI drift gate flips to
hard-fail; the GHCR image is multi-arch so Apple Silicon Macs and
arm64 clouds pull native. Migrations 0043–0060 are additive and
forward-only. `PROMPT_VERSION` 4.24.0 → 4.25.0 with GROUND RULE 9
(Coach refuses GLP-1 dose recommendations) and GROUND RULE 15 (Coach
refuses drug-level estimates with MDR + MDCG 2021-24 cites).
### Added
@@ -31,13 +42,13 @@ GROUND RULE 9 (Coach refuses GLP-1 dose recommendations).
`/insights/schlaf` renders awake / REM / core / deep as stacked
columns over the last 7 / 14 / 30 nights, sourced from the
`sleepStage` column on `Measurement`.
- **Targets (`/insights/zielwerte`) redesigned — 10-phase rewrite
with a conditional Coach-handoff card.** New `<TargetCard>`
primitive replaces the v1.4.22 layout. Page-level consistency
strip pins meta context above the cards. The Coach-handoff card
only renders when an AI provider is configured; rule-based mode
hides it cleanly. Mobile-first three-column grid; per-card cog
for visibility toggles.
- **Targets (`/insights/zielwerte`) redesigned with a conditional
Coach-handoff card.** New `<TargetCard>` primitive replaces the
v1.4.22 layout. Page-level consistency strip pins meta context
above the cards. The Coach-handoff card only renders when a
language-model provider is configured; rule-based mode hides it
cleanly. Mobile-first three-column grid; per-card cog for
visibility toggles.
- **GLP-1 medication tracking — full integration across ten
surfaces.** New `Medication.treatmentClass` enum (`GLP1`,
`STANDARD`); `MedicationDoseChange` history table; `InjectionSite`
@@ -87,25 +98,110 @@ GROUND RULE 9 (Coach refuses GLP-1 dose recommendations).
Health / manual) and `asOf` timestamp behind the score number.
`aria-labelledby` panel pairing for screen readers.
- **Four additional locales — French, Spanish, Italian, Polish.**
AI-initial translation bundles plus `<MaintainershipBanner>` on
every page that warns the locale is not actively maintained and
links to a GitHub issue for corrections. EN + DE stay maintained;
FR / ES / IT / PL ride the EN Coach + insights system prompts
via a one-line `REPLY LANGUAGE` footer until native rewrites
ship. Locale picker covers all six; signup browser-detect maps
`fr|es|it|pl` to the corresponding bundle.
First-party translation bundles cover the full string surface
with a `<MaintainershipBanner>` flagging the locale as
community-maintained and pointing at the translation-feedback
issue template. Coach + insights system prompts ship as native
per-locale bodies (no `REPLY LANGUAGE` footer indirection) with
the full safety-contract matrix in YAML per locale + structural
refusal-probe coverage in CI (see Tests). EN + DE stay
Marc-maintained. Locale picker covers all six; signup
browser-detect maps `fr|es|it|pl` to the corresponding bundle.
- **Coach — native first-party system prompts across six locales.**
Coach + insights system prompts move from EN body + `REPLY
LANGUAGE` footer to native per-locale bodies; the safety contract
matrix lives as YAML per locale (single source of truth across
drafting + tests). The refusal-probe matrix in CI catches
cross-locale prompt regressions before they reach the dispatch
surface.
- **VO2 max dashboard trend tile (opt-in).** Secondary-metric
pattern — default-invisible, surfaces only when the user has
VO2 max data from Apple Health.
- **Personal-record schema + listing endpoint.** Migration 0054
+ `PersonalRecord` table + `PersonalRecordDirection` helper
+ `GET /api/personal-records` (paginated). Detection worker
deferred to v1.4.26; the table is queryable today.
- **Workout + WorkoutRoute schema (HKWorkout-aligned).**
Migration 0053 + Zod boundary in `src/lib/validations/workout.ts`
with a twenty-member sport-type union and a `GeoJSON LineString`
route column in JSONB. Ingest endpoint scheduled to land
alongside the v1.5 iOS client.
- **Personal Records end-to-end — schema, detection worker, badge,
push opt-in.** Migration 0054 introduces the `PersonalRecord`
table + `PersonalRecordDirection` helper; `GET
/api/personal-records` is paginated (default twenty-five, max two
hundred). A pg-boss worker sweeps MAX / MIN per metric and the
workout slots on every batch-ingest + a thirty-minute fallback
cron (concurrency five, warmup gate so the very first datapoint
per metric does not promote itself). Metric trend tiles render a
PR badge when the record landed in the last thirty days, with
WCAG-AA contrast in dark mode. A per-user push opt-in toggle
(default off) wires into the existing dispatcher cascade.
- **Workouts — schema + typed batch-ingest endpoint.** Migration
0053 introduces the `Workout` + `WorkoutRoute` tables; the Zod
boundary in `src/lib/validations/workout.ts` covers a
twenty-member sport-type union and a `GeoJSON LineString` route
column in JSONB. `POST /api/workouts/batch` accepts up to five
hundred rows per call, wraps `withIdempotency()`, returns the
same `inserted | duplicate | skipped` envelope as the
measurements batch, and reconciles inserted vs duplicate counts
correctly under contention. Rate-limited and size-capped.
- **Onboarding rebuilt as a nested-route wizard.** The legacy
v1.4.20 onboarding ships as a six-step wizard at
`/onboarding/[step]` powered by a new `<OnboardingShell>`
primitive: welcome carousel with value-prop slides, goals
chip-picker, source selection 4-card grid (with Apple Health
marked coming-soon), baseline form, source-connect step, and a
done step. `User.onboardingStep` (Migration 0057, with
Migration 0060 backfilling and flipping the column to
`NOT NULL DEFAULT 0`) drives resume-state; a welcome-back banner
surfaces on the entry page when an in-progress wizard is
detected. `POST /api/onboarding/step` advances the wizard with
rate-limit + audit, returns 409 on a concurrent advance to close
the read-then-write race. The marketing entry-point swaps from
the v1.4.20 single-page flow to the new wizard. Five locales
carry the shell key surface (six total).
- **GLP-1 — EMA drug knowledge layer.** A first-party drug-knowledge
module covers five GLP-1 / GIP-GLP-1 drugs with EPAR-sourced
half-life, recommended titration step, brand-name table, and a
brand-to-id lookup. A drift-guard test pins the values against
the EPAR + Psp 4.13099 references on disk so the module cannot
silently drift.
- **GLP-1 — Research Mode with estimated drug-level chart, MDR
acknowledgment dialog, and Settings toggle.** A pure
one-compartment pharmacokinetic helper produces qualitative
drug-level traces from the user's injection history (steady-state
approximation, observational use only). The chart renders on the
GLP-1 medication detail page behind a Research-Mode gate:
`User.researchModeAck*` columns (Migration 0058) record the
acknowledgment version + timestamp; a dialog explains the
EU MDR 2017/745 + MDCG 2021-24 context and cites EMA EPAR plus
Schneck / Urva 2024 before the user can opt in.
`GET / POST / DELETE /api/auth/me/research-mode` carries the
acknowledgment lifecycle. A Settings → Advanced toggle exposes
the opt-in plus a re-prompt banner whenever the acknowledgment
version bumps.
- **GLP-1 — side-effect logging with a 21-entry × 5-category
taxonomy.** Migration 0059 introduces the `MedicationSideEffect`
table; pure helpers produce the taxonomy + a five-point Likert
severity scale; a section on the GLP-1 detail page lets the user
log entries with category + severity + free-text notes. Category
is derived server-side from the entry (no client-supplied
category) so the taxonomy cannot drift between client and server.
- **GLP-1 — cadence visualisation + compliance chips.** Pure
helpers expand a medication's schedule into expected slots,
pair each slot with the closest actual intake, and produce a
cadence timeline + compliance chip strip on the detail page.
`GET /api/medications/[id]/cadence` returns the structured
payload; the helpers honour per-user timezone so cross-midnight
doses bucket correctly regardless of locale.
- **GLP-1 — EMA titration ladder display.** Pure helpers walk the
EMA-sourced titration schedule from the knowledge layer; the
GLP-1 detail page renders the user's current step + remaining
ladder as observational reference, framed as reference-not-advice
and bound by GROUND RULE 15. `GET
/api/medications/[id]/titration-ladder` returns the structured
ladder + current step.
- **GLP-1 — pen-and-vial inventory with a 30-day in-use clock.**
Migration 0056 introduces the `MedicationInventoryItem` table +
a pure state machine (SEALED → IN_USE → EXPIRED) with a 30-day
in-use clock from `markAsFirstUseAt`. An inventory card on the
medication detail page surfaces the active pen, days remaining,
and SEALED stock; intake events decrement the dose count, and a
daily 03:00 cron flips expired in-use items in a single
`updateMany`. Re-runs the state machine on every PATCH so a
back-dated first-use immediately moves a stale pen to EXPIRED.
- **Apple Health identifier mapping (server-side).** Ports the
identifier table from `k0rventen/apple-health-grafana` and
`dogsheep/healthkit-to-sqlite` with MIT and Apache-2.0
@@ -127,6 +223,21 @@ GROUND RULE 9 (Coach refuses GLP-1 dose recommendations).
the latency goes from ~1 h polling to seconds. OAuth scope
upgraded to include `user.activity` and an in-app banner
prompts existing users to reconnect once.
- **Withings — Activity sync (steps + distance + active-energy).**
Calls `getactivity`, ingests one row per day, anchors at noon UTC
so positive-offset users bucket cleanly into their local day.
Backed by a pg-boss `activity-sync` queue plus a webhook enqueue
hook on the new subscription channel.
- **Withings — Sleep v2 sync (stage-level segments).** Calls
`sleepv2_get` and writes per-night stage segments through the new
`sleepStage` composite (Migration 0055 widens the Measurement
unique index to include `sleep_stage` with `NULLS NOT DISTINCT`).
Backed by a pg-boss `sleep-v2-sync` queue plus webhook enqueue.
The sleep sub-page's stacked-bar chart renders the segments
directly.
- **Withings — webhook subscriptions expanded to activity + sleep
v2.** Subscription registration plus webhook delivery routing for
the two new event types.
- **Admin Login overview — location, provider and CSV polish.**
Adds a location column derived from the audit IP; adds a
provider column distinguishing passkey, password, API-token
@@ -141,6 +252,16 @@ GROUND RULE 9 (Coach refuses GLP-1 dose recommendations).
`ubuntu-24.04-arm` and merges the manifest. Apple Silicon
Macs and arm64 clouds now pull native; the previously-stale
README claim is accurate again.
- **GLP-1 endpoint hardening.** `POST
/api/medications/[id]/glp1` now parses through bounded Zod
schemas (`glp1DoseChangePostSchema`, `glp1InventoryPostSchema`)
with length-capped notes, finite-number guards on dose value,
and bounded `effectiveFrom`. Every write produces an audit-log
row and the route inherits the 30-per-minute-per-user rate
limit from the rest of the medication surface.
- **Translation-feedback issue template.** GitHub issue template
for community translation corrections, paired with the
maintainership banner copy on FR / ES / IT / PL surfaces.
- **Repository polish.** README hero rewrite (what / who / try
the demo, thirty-second read). Topics expanded 10 → 18.
Branch protection v2 with conversation-resolution. GitHub
@@ -156,7 +277,7 @@ GROUND RULE 9 (Coach refuses GLP-1 dose recommendations).
- **Targets page-shell unified with the rest of Insights.**
Single consistency strip + Coach handoff + per-card actions
rather than the v1.4.22 sparkline-everywhere layout.
- **AI Coach default-window preference plus chip-order rationalised.**
- **Coach default-window preference plus chip-order rationalised.**
The window picker now persists per-user (last seven / thirty /
ninety days); suggested-prompt chips reorder so health-focus
chips lead and meta-discovery chips trail. Composer auto-grows
@@ -177,9 +298,47 @@ GROUND RULE 9 (Coach refuses GLP-1 dose recommendations).
three sections.** Every section header pairs an icon with a
heading; the v1.4.24-era split (icon-only on some, heading-only
on others) is gone.
- **`PROMPT_VERSION` 4.24.0 → 4.25.0** with new GROUND RULE 9
in EN + DE forbidding GLP-1 dose recommendations. Coach falls
back to a clinical-referral framing on any dose question.
- **`PROMPT_VERSION` 4.24.0 → 4.25.0** carries two new safety
ground rules across all six locales: GROUND RULE 9 forbids GLP-1
dose recommendations (Coach falls back to a clinical-referral
framing on any dose question); GROUND RULE 15 forbids drug-level
estimates and cites EU MDR 2017/745 + MDCG 2021-24 in the
refusal copy. Coach + insights system prompts ship as native
bodies per locale rather than the legacy `REPLY LANGUAGE`
footer.
- **Dashboard top-tile polish.** Tile headings collapse to a
single line per locale (BP / BF abbreviations land in EN + DE +
the four Romance locales); the trend arrow moves inline next to
the headline value; the value row baseline-aligns across tiles
regardless of font fallback. Regression guard pins the baseline
contract.
- **OpenAPI drift gate flips to hard-fail.** `pnpm openapi:check`
CI step now red-bars a PR on any drift between the Zod registry
and `docs/api/openapi.yaml`. The v1.4.23 warn-only window
closes; iOS DTO codegen can rely on the spec being authoritative.
- **Coolify auto-deploy gate exposed as an explicit
maintainer-toggleable repo variable.** `vars.COOLIFY_AUTO_DEPLOY`
is now a `true | false` switch in the deploy workflow instead of
the implicit secret-presence gate that silently no-op'd in
v1.4.21-23.
- **Settings → Sources sentinel cleanup.** The `__default__`
device-type bucket sentinel is retired in favour of a null
bucket at the storage layer; the Settings UI reads cleaner and
the reorder helpers (`reorderLadder`) collapse the two
`moveSource` + `moveDeviceType` paths into one.
- **Section wrapper for medication detail.** A new
`<MedicationDetailSection>` wraps the three medication-detail
shells (titration, scheduling, side-effects) plus the inventory
disclosure top so cross-section chrome stays consistent and
future sections drop in without copy-paste.
- **Shared route ownership helper.** Nine medication routes
(`titration`, `side-effects`, `inventory`, `cadence`, `intake`,
`compliance`, `phase-config`, `api-endpoint`, plus the GLP-1
convenience route) now call `assertMedicationOwnership` from
`src/lib/medications/route-guards.ts` rather than open-coding
the lookup; rate-limit headers across the same surface align
on the option-bag form `apiError(..., { headers:
rateLimitHeaders(rl) })`.
### Fixed
@@ -192,13 +351,14 @@ GROUND RULE 9 (Coach refuses GLP-1 dose recommendations).
was subtracting the wrong-period baseline on several charts;
baseline lookups now match the caption window. Three regression
guards.
- **AI prose metric-token leaks** — `metric:<TYPE>` raw tokens
surfaced in three places that escaped the v1.4.22 sweep
(`<RecommendationCard>` fallback path, briefing keyFinding
helper, and Coach in-flight bubble). `stripChartTokens()` regex
widened to cover lowercase prose remnants; prompt-side GROUND
RULES 8 and 13 reaffirm the constraint. PROMPT_VERSION 4.23.0
→ 4.24.0 carried the prompt change; 4.25.0 inherits.
- **Raw metric-token leaks in generated prose** — `metric:<TYPE>`
identifiers surfaced in three surfaces that escaped the v1.4.22
sweep (`<RecommendationCard>` fallback path, briefing
`keyFinding` helper, and Coach in-flight bubble).
`stripChartTokens()` regex widened to cover lowercase prose
remnants; prompt-side GROUND RULES 8 and 13 reaffirm the
constraint. PROMPT_VERSION 4.23.0 → 4.24.0 carried the prompt
change; 4.25.0 inherits.
- **Withings BP and temperature webhook latency.** Webhook
subscription now covers BP and temperature endpoints, so the
earliest a measurement reaches the user drops from ~1 h to
@@ -260,19 +420,135 @@ GROUND RULE 9 (Coach refuses GLP-1 dose recommendations).
- **Personal-records endpoint unpaginated.** `GET
/api/personal-records` now clamps `?limit` (default twenty-five,
maximum two hundred); the unbounded read is gone.
- **Withings activity sync bucketed positive-offset users into the
wrong day.** The per-day row was anchored at 23:59:59 UTC, so a
user in `Pacific/Auckland` saw activity rows attached to the
following local day. Anchored at noon UTC (`T12:00:00.000Z`)
with a regression suite covering Berlin / Los Angeles / Tokyo /
Auckland.
- **Cadence and compliance helpers ignored per-user timezone.**
`expandScheduleSlots`, `pairDoses`, `buildCadenceTimeline` and
the compliance-chip helper now accept a `timeZone` argument that
threads through `Intl.DateTimeFormat`, so cross-midnight doses
bucket correctly regardless of user locale. The cadence route
resolves through `resolveUserTimeZone(user)`.
- **Inventory state-machine ignored back-dated first-use.** Issuing
a PATCH that pushed `markAsFirstUseAt` more than thirty days into
the past left the item in IN_USE; the state machine now re-runs
on every PATCH and the stale item flips to EXPIRED. Regression
test pins the contract.
- **Inventory expire-stale cron looped per row.** Replaced the
row-by-row `prisma.update` loop in `expireStaleInUseItems` with a
single `updateMany`; bulk-update contract pinned by test.
- **Onboarding step write was read-then-update.** A concurrent
advance could double-step the wizard. The update now conditions
on `{ id, onboardingStep: current, onboardingCompletedAt: null }`
and returns 409 on conflict.
- **Workout schema accepted `endedAt <= startedAt`.**
`createWorkoutSchema` gained a `.superRefine` rejecting
zero-duration and inverted windows; the PR detection worker's
MIN-direction slot also guards against `durationSec === 0` so a
malformed row cannot promote itself to a personal record.
- **Personal Record detection duplicate writes under contention.**
Same-millisecond writes into a null workout slot now reconcile
to a single row; regression test seeds the race.
- **Source-priority parse failures were silent.** Storage parse
errors now route through an observer breadcrumb and the resolved
blob is frozen at construction so downstream mutation is caught
immediately.
- **Health-Score `asOf` derived non-deterministically.** The
`asOf` timestamp now derives from the input dates instead of
the wall clock so repeated computes against the same dataset
produce stable timestamps.
- **Source-priority audit-log entries missed two write paths.**
Doctor-report-prefs and source-priority PUTs were not landing
audit-log rows; both write paths emit now.
- **Personal-record badge dark-mode contrast.** Lifted to WCAG-AA
contrast on the `<PersonalRecordBadge>` surface.
- **Range-bar zone backgrounds.** `<RangeBar>` mixed Tailwind raw
palette tokens with Dracula tokens; all zone backgrounds now
resolve through Dracula tokens with the pinned test rewritten.
- **Research-Mode acknowledgment dialog footer scrolled below
fold.** The footer pins outside the scrolling region on small
viewports.
- **44-px touch-target floor across the onboarding wizard.**
Shell back/skip/next, carousel pager, source-card grid, baseline
and goals-chip CTAs all hit the WCAG 2.5.5 floor without
changing visual density at typical rendering sizes.
- **Workout endpoint reconciled inserted-vs-duplicate counts
incorrectly under contention.** Race-recovery path produced
inconsistent envelope counts; corrected with a regression test
seeding the contention window.
- **Legacy Withings `?secret=` webhook form had no counter.** An
in-memory `withings.webhook.legacy_form_total` counter surfaces
through `ops-stats`; the warning text gained the re-subscription
URL for affected accounts.
- **Acknowledgment dialog i18n back-button.** Medication history
page back-button hard-coded `Zurück`; routed through `t()`.
- **Sleep + audio-exposure + comparison-hint i18n keys.** Backfill
pass picked up the keys missed by the v1.4.22 sweep across all
six locales.
- **Build resolution for safety-contract YAML.** The loader now
resolves the YAML matrix path from `cwd` rather than `__dirname`
so the build does not break when the bundler reshapes the
module-relative root.
- **GLP-1 drift guard self-skips when the EMA research file is
absent.** Local checkouts without the reference file no longer
red-bar the test.
- **`x-axis` chart ticks resolved in user timezone.**
`xAxisTicks` now renders the tick formatter in
`user.displayTimezone` instead of hard-coding Europe/Berlin.
- **Chart token references switched to `var(--token)` form.**
Recharts strokes were still using `hsl(var(--token))`; switched
to bare `var(--token)` so Tailwind v4's color-mix parser
resolves them.
- **`detectGlp1Plateau` branches lacked direct coverage.** Added
Prisma-mocked unit tests covering the previously-uncovered
branches.
### Security
- **Coach refuses GLP-1 dose recommendations.** New GROUND RULE 9
in EN + DE — the Coach surfaces a clinical-referral message on
any dose adjustment ask, never a number. PROMPT_VERSION 4.25.0
carries the rule.
- **Withings webhook secret no longer leaks into structured
logs.** `WITHINGS_WEBHOOK_SECRET` was landing as a URL
path-segment in `http.path` on every Wide Event the request
produced. The logging stack now carries a parameterised
`PATH_SECRET_PATHS` registry seeded with the
`/api/withings/webhook/[token]` shape, and
`WideEventBuilder.setHttp` rewrites path + route segments to
`[REDACTED]` before they reach stdout / the in-memory ring
buffer / Loki. Existing query-string redaction is unaffected.
- **Coach refuses GLP-1 dose recommendations.** GROUND RULE 9
across all six locales — the Coach surfaces a clinical-referral
message on any dose adjustment ask, never a number.
PROMPT_VERSION 4.25.0 carries the rule.
- **Coach refuses drug-level estimates with regulatory cites.**
GROUND RULE 15 across all six locales — the Coach refuses any
ask for an estimated drug level or pharmacokinetic prediction,
cites EU MDR 2017/745 + MDCG 2021-24 in the refusal copy, and
routes the user to the Research Mode disclosure flow.
Adversarial refusal-probe matrix exercises 20+ paraphrasings per
GROUND RULE across six locales in CI on every push, so
prompt-injection regressions surface before reaching the
dispatch surface.
- **Research Mode is gated by an MDR acknowledgment dialog.** The
estimated drug-level chart on the GLP-1 detail page renders only
after the user opts in through a dialog that cites
EU MDR 2017/745 + MDCG 2021-24, EMA EPAR and the Schneck / Urva
2024 pharmacokinetic reference. The acknowledgment version
re-prompts the user on bump.
- **GLP-1 convenience endpoint hardened.** `POST
/api/medications/[id]/glp1` parses through bounded Zod schemas
(`glp1DoseChangePostSchema`, `glp1InventoryPostSchema`), enforces
length caps on notes, finite-number guards on dose value, and
bounded `effectiveFrom`; every write produces an audit-log row
and the route inherits the 30-per-minute-per-user rate limit
from the rest of the medication surface.
- **GLP-1 medication strings sanitised before LLM prompt
interpolation.** Patient-safety: a malicious or malformed
medication name embedded prompt-injection bait. Sanitiser
passes Latin-letter + digit + common punctuation only.
interpolation.** A malicious or malformed medication name no
longer reaches the prompt body verbatim. Sanitiser passes
Latin-letter + digit + common punctuation only.
- **Batch-ingest rate limit (sixty per minute per user default).**
`POST /api/measurements/batch` and the new
`POST /api/measurements/batch`, `POST /api/workouts/batch` and
`DELETE /api/measurements/by-external-ids` carry a token-bucket
rate limit returning the standard 429 envelope.
- **Audit-log writes on source-priority and doctor-report-prefs
@@ -287,7 +563,7 @@ GROUND RULE 9 (Coach refuses GLP-1 dose recommendations).
paths; one user's priority blob never enters another user's
resolver.
### Refactor
### Refactor / Hygiene
- **`pickCanonicalSource` becomes a two-axis lookup with single-
axis fallback.** Reuses `getDeviceTypeLadder()` so the
@@ -306,63 +582,159 @@ GROUND RULE 9 (Coach refuses GLP-1 dose recommendations).
- **`HK_QUANTITY_TYPE_TO_MEASUREMENT` removed.** The legacy view
was a redundant projection of `APPLE_HEALTH_TYPE_MAP`; all
callers go through the canonical map now.
- **W7e dead-code cleanup pass.** Drops `<InsightsPageHero>`
(zero callers since v1.4.20), `<IntakeTimeline>`,
`<ComplianceCharts>` wrapper and three orphan `insightsGeneralStatus`
query keys from the v1.4.16-era hero. Removes the orphan
- **Dead-code cleanup pass.** Drops `<InsightsPageHero>` (zero
callers since v1.4.20), `<IntakeTimeline>`, `<ComplianceCharts>`
wrapper and three orphan `insightsGeneralStatus` query keys from
the v1.4.16-era hero. Removes the orphan
`/api/insights/general-status` route (superseded by
`<InsightAdvisorCard>` since v1.4.16).
- **Coach `<CoachDrawer key={prefill}>` controlled-prop refactor
is fully in place** — the v1.4.24 follow-up of `useResettableValue`
is now the only call path; the legacy remount hack code is gone.
- **380 dead i18n keys dropped.** Runtime probe across the six
locales identified 380 keys with zero call sites and zero runtime
resolutions; all six locale bundles are smaller by that count.
Top namespaces: `settings`, `admin`, `classifications`,
`medications`. The remaining ~148 keys are queued for a second
pass in v1.4.26.
- **Dead system-prompt constants removed.** `BASE_SYSTEM_PROMPT`
and `INSIGHTS_SYSTEM_PROMPT` constants had no remaining
importers after the native per-locale prompt move; both deleted.
- **`useInsightStatus` hook extracted.** The four insights
sub-page status queries collapsed into a single hook so the
duplicated `useQuery` + Suspense fallback shape lives in one
place.
- **`<MedicationDetailSection>` wrapper.** Three medication-detail
shells (titration, scheduling, side-effects) and the inventory
disclosure top share a single wrapper; future medication detail
sections drop in without copy-paste.
- **Shared helpers across the medication + onboarding surfaces.**
New modules `src/lib/api/read-error.ts`,
`src/lib/medications/route-guards.ts`,
`src/lib/medications/research-mode-types.ts` and
`src/lib/medications/dose-string.ts` consolidate previously
duplicated patterns. Three near-identical dose-string parsers
collapse to one `parseDoseMg`; four-times-duplicated
`readError` collapses to one; nine medication routes call the
shared `assertMedicationOwnership`.
- **Side-effect taxonomy drift-guard.** `SIDE_EFFECT_CATEGORY_VALUES`
and `SIDE_EFFECT_ENTRY_VALUES` now derive from the Prisma enum
via `z.nativeEnum`; a drift-guard test pins Prisma enum ↔
taxonomy map ↔ validator triangle so the three cannot drift.
- **Side-effect category derived server-side.** The
`createSideEffectSchema` no longer accepts a client-supplied
`category`; the server derives it from the entry. Removes the
defensive 422 path and the cross-tier drift surface.
- **Dashboard top-tile baseline alignment.** Inline trend arrow +
baseline-aligned value row across all dashboard tiles, with the
baseline contract pinned by regression test.
- **Range-bar Dracula token swap.** `<RangeBar>` zone backgrounds
resolve through Dracula tokens with the pinned test rewritten;
raw palette references gone.
- **44-px touch-target floor across the top-bar + section
strips.** WCAG 2.5.5 alignment across the broader navigation
surface, complementing the onboarding-specific pass.
- **Cat-C typo + naming polish from the W10 review pass.** Minor
identifier renames flagged by the dead-code probe; no functional
change.
- **`safeRequestProp` widened catch.** Narrowed catch broadened to
tolerate `undefined` requests after the hygiene-review pass
surfaced the regression.
- **`pickCanonicalWorkout` helper for cross-source workout
dedup.** Source-priority logic centralised for the v1.5 iOS
workout-ingest path.
- **Insights regenerate button relocated; tab strip lifted.**
Tab-strip extraction + relocation of the regenerate button to
the top-right, paving the way for the seven sub-page surfaces.
### Tests
- 2244 → 2652 passing unit tests (+408). Integration suite
140 → 174 (+34). e2e green on the W2 CI fix (coach-prefs URL
mock + Pixel-5 selector hardening).
- New unit suites: GLP-1 plateau-detection text formatter; two-axis
source-priority resolution (single-axis fallback, per-device
override, mixed-bucket selection); source-priority Settings
reducer (reorder, reset, validate); medication-card GLP-1
variant + injection-site-picker (RTL); doctor-report prefs
PUT contract; personal-records pagination clamp;
`berlinIsoWeekday` timezone-aware suite; sleep-stage chart
i18n-suffix drift-guard; Health-Score provenance i18n
drift-guard; api-handler private-field crash regression.
- New integration suites: two-axis canonical-source resolution
- 2244 → 3828 passing unit tests across 344 files (+1584; one
pre-existing skip carries through). Integration suite 140 → ~170
across 11 files. e2e green on the W2 CI fix (coach-prefs URL
mock + Pixel-5 selector hardening) plus the Fix-I hot-fix that
re-anchored the dashboard insight-card and the mobile x-axis
tick locators.
- **Coach refusal-probe matrix.** 1800+ assertions exercise 15
GROUND RULES across six locales with 20+ adversarial
paraphrasings each. CI runs the matrix on every push; any
prompt-injection or jailbreak regression surfaces before
reaching the dispatch surface. Drift-guard tests pin the
YAML-per-locale safety-contract matrix in lockstep with the
validator-derived enums.
- **New unit suites (selection):** GLP-1 plateau-detection text
formatter; two-axis source-priority resolution (single-axis
fallback, per-device override, mixed-bucket selection);
source-priority Settings reducer (reorder, reset, validate);
medication-card GLP-1 variant + injection-site-picker (RTL);
doctor-report prefs PUT contract; personal-records pagination
clamp; `berlinIsoWeekday` timezone-aware suite; sleep-stage
chart i18n-suffix drift-guard; Health-Score provenance i18n
drift-guard; api-handler private-field crash regression; pure
cadence + compliance helpers with TZ assertions across UTC+0,
UTC+9, UTC-8; side-effect taxonomy drift-guard; EMA titration
ladder helpers; pen-inventory state-machine + 30-day clock
helpers; PR detection worker (MAX / MIN per metric + workout
slots + warmup gate + null-slot dup regression + zero-duration
guard); Withings activity TZ regression across Berlin / Los
Angeles / Tokyo / Auckland; Withings sleep v2 segment ingest;
log-redaction path-segment rule + `setHttp` rewrite; GLP-1
endpoint Zod + audit + rate-limit; onboarding step concurrent-
advance regression; inventory PATCH state-machine back-dated
first-use regression; bulk-update contract for the expire-stale
cron; brand-name guard + sentinel-preservation + safety-contract
parity for the YAML matrix; locale auto-discovery + fallback-
chain runtime guard.
- **New integration suites:** two-axis canonical-source resolution
end-to-end; `requireAuth()` narrow-scope on unscoped route;
batch-ingest race reconciliation under contention.
batch-ingest race reconciliation under contention; workout-batch
race + size-cap + ingest end-to-end; PR-detection end-to-end;
cross-source priority for Withings Activity + Apple Health;
Withings sleep-stage composite ingest; per-user timezone end-to-
end (Pacific/Auckland).
- **Migrations:** 9 new (0051–0059) + 1 hardening (0060
`onboarding_step_not_null` backfill + NOT NULL flip). All
additive and forward-only; migration 0057 comment rewritten to
match PG11+ fast-path ADD COLUMN DEFAULT semantics.
### Deferred to v1.4.26
See `.planning/v1426-backlog.md` for the full thirty-plus item
list with effort estimates and source citations. Headline items:
See `.planning/v1426-backlog.md` for the full backlog with effort
estimates and source citations. Headline items:
- OpenAPI drift-gate hard-fail flip (still warn-only after this
release; iOS DTO codegen needs hard-fail confidence before
the v1.5 iOS sprint starts).
- Onboarding rebuild promoted from v1.5 (last release where new
users see the v1.4.20-era onboarding without sub-pages, GLP-1,
six locales).
- Native FR / ES / IT / PL Coach + insights system prompts
(current `REPLY LANGUAGE` footer is interim; native bodies
let each safety contract speak in its target voice).
- Personal-record detection worker (schema shipped this release;
sweep-on-insert + nightly cron pending).
- Workout ingest endpoint (`POST /api/measurements/batch?kind=workout`
— schema shipped, endpoint waits on iOS contract finalisation).
- Withings Activity sync routine (OAuth scope shipped; sync
waits on reconnect-banner adoption window).
- Withings Sleep v2 routine.
- 414 dead i18n keys cleanup (top namespaces: `settings:114`,
`admin:75`, `classifications:69`, `medications:21`).
- `User.onboardingGoals` column + server-side persistence for the
goals chip-picker selection (today the picker holds the choice
client-side only; the dashboard-widgets seed feature reads it in
v1.4.26).
- `advance()` hook extraction for the three onboarding step
components — pattern surfaced in the simplifier review, deferred
to avoid collision with v1.4.25 touch-target work.
- `glp1-pk.ts` unused-export decision (internalise vs wire
dashboard chip).
- Seven orphan endpoint go / no-go decisions
(`/api/admin/ai-settings`, `/api/admin/backup/test`,
`/api/admin/status-overview`,
`/api/monitoring/{glitchtip,umami}/test`).
- ~148 dead i18n keys remaining after the W10 runtime-probe sweep
(the v1.4.25 pass removed 380 of the 528 candidates; the
remaining ~148 need second-pass call-site verification).
- FR / ES / IT / PL prose hand-review by a native speaker for the
Coach + insights surface (the structural refusal-probe matrix
covers safety; the user-facing prose still benefits from a
human pass).
- Coach `lastYear` baseline + row-tap-to-prefill polish.
- Sleep sub-page stacked-column visual polish (stage colours +
legend density).
- Mood verbal-labels persistence behind a per-user toggle.
- Drug-level chart-side 90-day staleness clock wiring (the
acknowledgment dialog already re-prompts on version bump and
Coach refuses at GROUND RULE 15; the clock is defence-in-depth).
- iOS-18 long-tail HK identifier mappings (sleep apnea, GAD-7 /
PHQ-9, running form, paddle / row / ski distances, pregnancy /
cycle, FHIR clinical).
- VO2 max chart-row card on `/insights/<metric>` (dashboard tile
shipped; chart card queued alongside iOS body-composition page).
- Withings webhook secret moved from query string to header form.
shipped; chart card queued alongside the iOS body-composition
page).
- Lazy-loaded locale JSON bundles (all six locales import
synchronously at present, ~675 KB to every client).