diff --git a/docs/audit/v1425-summary.md b/docs/audit/v1425-summary.md new file mode 100644 index 000000000..b2e04c6e5 --- /dev/null +++ b/docs/audit/v1425-summary.md @@ -0,0 +1,463 @@ +# HealthLog v1.4.25 — release summary + +## Release brief + +v1.4.25 is the largest feature delta in the v1.4.x line and the first time the +loud / polish / foundation three-beat ran through a multi-cluster +expansion on top of the v1.4.20-introduced develop-to-main release-merge model. +Insights expands from a single mother page into seven dedicated metric +sub-pages with a shared tab strip; GLP-1 medication tracking lands +end-to-end across the dashboard tile, the weight-chart marker overlay, +the therapy timeline, the plateau-detection rule in the daily briefing, +the dedicated doctor-report section, a Research-Mode drug-level chart +behind an MDR acknowledgment dialog, an EMA-sourced drug knowledge layer, +the EMA titration ladder, cadence visualisation, compliance chips, a +21-entry by 5-category side-effect taxonomy, and a pen-and-vial +inventory with a 30-day in-use clock; the cross-source priority resolver +becomes a two-axis metric × deviceType lookup with a per-user Settings +surface; per-user timezone threads through ten analytics and presentation +surfaces; Withings coverage roughly doubles with twelve new measurement +types, webhook-driven BP / temperature ingestion, plus the new Activity +sync (steps + distance + active-energy) and Sleep v2 sync (stage-level +segments); the onboarding flow rebuilds as a nested-route wizard with a +welcome carousel, goals chip-picker, source selection grid, baseline +form, and a welcome-back resume banner; Personal Records ship end-to-end +with a pg-boss 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 bound by a +1800-assertion refusal-probe matrix on every push; the OpenAPI drift gate +flips to hard-fail so the v1.5 iOS DTO codegen can rely on +`docs/api/openapi.yaml` being authoritative; and the GHCR image is now +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 carries GROUND RULE 9 (Coach refuses GLP-1 dose +recommendations) and GROUND RULE 15 (Coach refuses drug-level estimates +with explicit EU MDR 2017/745 + MDCG 2021-24 cites in the refusal copy). + +## Live state + +| Field | Value | +| ------------------ | ------------------------------------------------------------------------------- | +| URL | `https://healthlog.bombeck.io` | +| `/api/version` | `1.4.25` | +| GH release | https://github.com/MBombeck/HealthLog/releases/tag/v1.4.25 | +| Branch model | `develop` → `main` release-merge model, sixth tag through it (v1.4.20 onwards) | +| Tag commit | `17b8d8d2 chore(release): v1.4.25` | +| Predecessor | `v1.4.24` at `ca03c609` | + +## What shipped + +### Insights expansion — seven dedicated metric sub-pages + +`/insights` becomes a navigation hub. `/insights/blutdruck`, +`/insights/gewicht`, `/insights/puls`, `/insights/stimmung`, +`/insights/medikamente`, `/insights/bmi` and `/insights/schlaf` each +render the metric's full chart, range bands, trend annotations and +correlation rows beneath the mother-page hero. Tab strip lifts above +each chart for cross-metric scanning. Targets (`/insights/zielwerte`) +redesigns around a new `` primitive with a conditional +Coach-handoff card that hides cleanly in rule-based mode. Sleep +sub-page renders awake / REM / core / deep as a stacked column over +the last 7 / 14 / 30 nights, sourced from `Measurement.sleepStage`. + +### GLP-1 medication tracking — end-to-end across ten surfaces + +`Medication.treatmentClass` enum (`GLP1`, `STANDARD`), +`MedicationDoseChange` history table, an `InjectionSite` enum with +eight site values, and a body-map picker that proposes the next +rotation site from the last fourteen days. Dashboard tile carries +next-injection schedule plus week-over-week weight delta. Weight chart +gains vertical injection markers. Therapy timeline plots titration +alongside the weight trace. Plateau-detection in `/api/insights/briefing` +flags four-week stalls with referral framing. Doctor-report PDF gains +a dedicated GLP-1 section. Drug-level chart on the medication detail +page renders behind a Research-Mode gate; the acknowledgment dialog +cites EU MDR 2017/745 + MDCG 2021-24, EMA EPAR and Schneck / Urva 2024 +before the user can opt in. EMA titration ladder helpers walk the +sourced schedule and frame as reference-not-advice. Cadence + compliance +helpers expand the schedule into expected slots, pair each with the +closest actual intake, and render a timeline + chip strip; honour +per-user timezone so cross-midnight doses bucket correctly regardless +of locale. Side-effect taxonomy (Migration 0059) covers 21 entries × 5 +categories, severity Likert 1-5, with `category` derived server-side so +the taxonomy cannot drift between client and server. Pen-and-vial +inventory (Migration 0056) carries a SEALED → IN_USE → EXPIRED state +machine with a 30-day in-use clock from `markAsFirstUseAt`; a daily +03:00 cron flips expired in-use items in one `updateMany`. State +machine re-runs on every PATCH so a back-dated first-use immediately +moves a stale pen to EXPIRED. + +### Cross-source priority — two-axis architecture + +`User.sourcePriorityJson` (Migration 0048) stores the per-user resolver: +a single-axis ladder by metric type plus an optional per-device-type +override. `pickCanonicalSource()` walks the metric axis first; a +per-device override (e.g. "BP from Withings BPM Connect, weight from +Withings Body+") wins within that ladder. Defaults: cumulative + sleep + +HRV + RHR favour Apple Health → Withings → manual; point measurements +favour Withings → Apple Health → manual. The `__default__` sentinel is +retired in favour of a null bucket. Settings → Sources screen exposes +the ladder for drag-reorder per metric and per-device override picker. + +### Per-user timezone — Option B threaded through ten surfaces + +New `User.timezone` column defaults to `Europe/Berlin`. CSV exporter +emits ISO-8601 with offset; formatters honour the user's tz; Profile +picker covers all IANA zones; admin sets the default for new accounts; +signup detects the browser tz; doctor-report PDF dates use the user's +tz; chart x-axes take a `timezone` prop; Coach snapshot timestamps +land in the user's tz; `MoodEntry.tz` records local-day grouping for +weekday correlation (Migration 0044); the weight-weekday correlator +buckets in the user's tz. Cadence + compliance helpers honour the +same argument. + +### Withings — expanded coverage + Activity + Sleep v2 + +Twelve new `MeasurementType` values (HRV, body temperature, SpO2 v2, +VO2 max, fat-free mass, fat mass, muscle mass, skin temperature, +pulse-wave velocity, vascular age, visceral fat — Migration 0049). +Webhook coverage extended to BP and temperature so the latency +collapses from ~1 h polling to seconds. OAuth scope upgraded to include +`user.activity` with an in-app reconnect banner. Activity sync calls +`getactivity`, ingests one row per day, anchors at noon UTC so +positive-offset users bucket cleanly into their local day. Sleep v2 +sync 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 pg-boss `activity-sync` and `sleep-v2-sync` queues plus +webhook enqueue hooks on the new subscription channels. + +### Onboarding rebuild — six-step nested-route wizard + +`/onboarding/[step]` powered by a new `` primitive: +value-prop carousel, goals chip-picker, source 4-card selection grid +(Apple Health marked coming-soon), baseline form, source-connect step, +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` +conditions the update on `{ id, onboardingStep: current, +onboardingCompletedAt: null }` and returns 409 on concurrent advance +to close the read-then-write race. + +### Personal Records — schema, worker, badge, push opt-in + +Migration 0054 introduces the `PersonalRecord` table + +`PersonalRecordDirection` helper. `GET /api/personal-records` clamps +`?limit` (default twenty-five, max two hundred). A pg-boss worker +sweeps MAX / MIN per metric and the workout slots on every batch-ingest +plus 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 `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, +size-capped. + +### Coach — native first-party 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). +GROUND RULE 9 forbids GLP-1 dose recommendations; GROUND RULE 15 +forbids drug-level estimates and cites EU MDR 2017/745 + MDCG 2021-24 +in the refusal copy. The refusal-probe matrix in CI exercises 15 +GROUND RULES across six locales with 20+ adversarial paraphrasings +each — 1800+ assertions on every push. Drift-guard tests pin the +YAML matrix in lockstep with the validator-derived enums. + +### OpenAPI drift gate — hard-fail flip + +`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. + +### Multi-arch GHCR image + Coolify auto-deploy toggle + +GHCR publish workflow builds `linux/amd64` on `ubuntu-latest` plus +`linux/arm64` on `ubuntu-24.04-arm` and merges the manifest. Apple +Silicon Macs and arm64 clouds pull native. The Coolify auto-deploy +gate is now an explicit `vars.COOLIFY_AUTO_DEPLOY=true|false` +maintainer toggle in the deploy workflow instead of the implicit +secret-presence gate that silently no-op'd in v1.4.21-23. + +### Security cluster + +`WITHINGS_WEBHOOK_SECRET` no longer lands as a URL path-segment in +`http.path` on every Wide Event. The logging stack now carries a +parameterised `PATH_SECRET_PATHS` registry seeded with the +`/api/withings/webhook/[token]` shape; `WideEventBuilder.setHttp` +rewrites path + route segments to `[REDACTED]` before they reach +stdout / the in-memory ring buffer / Loki. Existing query-string +redaction is unaffected. `POST /api/medications/[id]/glp1` now parses +through bounded Zod schemas, enforces length caps on notes, +finite-number guards on dose value, bounded `effectiveFrom`, writes +an `auditLog` row on every change and inherits the 30-per-minute-per- +user rate limit from the rest of the medication surface. +Batch-ingest endpoints (`POST /api/measurements/batch`, +`POST /api/workouts/batch`, `DELETE /api/measurements/by-external-ids`) +carry a sixty-per-minute-per-user token-bucket rate limit. Source- +priority and doctor-report-prefs PUTs emit `AuditLog` rows. + +## Migrations + +| Migration | Purpose | +|---|---| +| 0051 `measurement_device_type` | Adds `deviceType` enum + column to `Measurement` for the two-axis source-priority resolver. | +| 0052 `apple_health_enum_extensions` | Adds `ENVIRONMENT_AUDIO_EXPOSURE`, `HEADPHONE_AUDIO_EXPOSURE` and `TIME_IN_DAYLIGHT` to `MeasurementType`. | +| 0053 `workout_and_route` | Introduces `Workout` and `WorkoutRoute` tables for the typed workout-batch ingest endpoint and the v1.5 iOS workout sync. | +| 0054 `personal_record` | `PersonalRecord` table + `PersonalRecordDirection` helper. Unique index is `NULLS DISTINCT` by convention; the detection worker carries the application-level idempotency guard. | +| 0055 `measurement_sleepstage_composite` | Widens the `Measurement` unique index to include `sleep_stage` with `NULLS NOT DISTINCT` (Postgres 16 native) so Withings sleep v2 segments dedup correctly. | +| 0056 `medication_inventory_item` | `MedicationInventoryItem` table for the GLP-1 pen/vial inventory with the SEALED → IN_USE → EXPIRED state machine. | +| 0057 `user_onboarding_step` | Adds `onboardingStep` nullable to `User` for the wizard resume-state. Originally nullable; superseded by Migration 0060. | +| 0058 `user_research_mode` | Adds `researchModeAck*` columns to `User` for the GLP-1 Research-Mode MDR acknowledgment. | +| 0059 `medication_side_effect` | `MedicationSideEffect` table for the 21-entry × 5-category taxonomy. | +| 0060 `onboarding_step_not_null` | Backfills existing `User.onboardingStep` rows to 0 and flips the column to `NOT NULL DEFAULT 0`. Closes the tri-state ambiguity left by 0057. | + +All ten are additive and forward-only. Migration 0057's comment was +rewritten to match PG11+ fast-path `ADD COLUMN DEFAULT` semantics +during the reconcile pass. + +## Test coverage delta + +| Suite | v1.4.24 | v1.4.25 | Delta | +|---|---|---|---| +| Unit | 2 244 | 3 828 (across 344 files) | +1 584 | +| Integration | 140 | ~170 (across 11 files) | +30 | + +The unit-test growth is dominated by the Coach refusal-probe matrix +(1800+ assertions across 15 GROUND RULES × 6 locales × 20+ paraphrasings) +plus the feature-cluster helper suites: GLP-1 plateau-detection text formatter; +two-axis source-priority resolution; source-priority Settings reducer; +medication-card GLP-1 variant + injection-site-picker; doctor-report +prefs PUT contract; personal-records pagination clamp; `berlinIsoWeekday` +timezone-aware suite; sleep-stage chart i18n 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. + +Integration: two-axis canonical-source resolution end-to-end; +`requireAuth()` narrow-scope on unscoped route; 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). + +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 class-independently. +Typecheck clean. Lint baseline carried (no new warnings). OpenAPI +drift-check clean against the regenerated spec. + +## Code quality gates + +The W21 review ran eight read-only reviewers in parallel: code, +security, design, senior-dev, simplifier, product-lead, dead-code, +i18n-runtime. Combined output was 103 findings (2 Critical, 20 High, +41 Medium, 40 Low) plus a strategic product-lead assessment. + +**Critical (2 / 2 applied)**: sec-C1 (Withings webhook secret leaking +into `http.path` on every Wide Event) and product-lead-C1 (CHANGELOG +frozen at the initial release-prep surface, omitting the bulk of the +later feature work). Both closed before tag — sec-C1 via Fix-J +(`PATH_SECRET_PATHS` registry + `setHttp` redaction); +product-lead-C1 via the W22 release-redo editorial pass. + +**High (20 / 20 applied)**: sec-H1 (GLP-1 endpoint missing Zod + audit ++ rate-limit) closed in Fix-K. Six design-Highs (onboarding tap targets, +range-bar palette, PR-badge contrast) closed in Fix-L. Two code-Highs +(inventory state machine bypass on PATCH, per-row expire loop) closed +in Fix-M. Seven simplifier-Highs (`readError` dup, `templateFill`, +`statusLabel` nested ternary, `ResearchModeStatus` shared type, +`findDrugIdByBrand`, `assertMedicationOwnership` across 11 routes, +`MedicationInventoryState` import) closed in Fix-N. Code-H2 (cadence + +compliance ignore per-user timezone) closed in Fix-O. Dead-H4 +(research-mode-staleness module never wired) resolved by deletion in +Fix-O. One product-lead-H1 (test count footer stale) and +product-lead-H2 (Deferred-to-v1.4.26 section lists already-shipped +items) closed in W22. + +**Medium (17 of 41 applied, 24 deferred to v1.4.26)**: 17 ship-now +Mediums landed across Fix-J through Fix-O; the deferred 24 split +between schema-shape decisions (`User.onboardingGoals` column; +`/api/audit-log` consumer decision), module-surface narrowing +(`glp1-pk.ts`, `glp1-knowledge.ts`, `scheduling/*` unused exports), +148 remaining dead i18n keys after the W15 380-key sweep, and seven +orphan endpoint go / no-go decisions that need maintainer call. Full +deferrals matrix lives in `.planning/phase-W21-reconcile-plan.md` lines +220-265. + +**Low (40 / 40 deferred to v1.4.27)**: appendix in the reconcile plan +covers them — none are user-facing; one (i18n-Low-1, e2e +`healthlog_locale` vs `healthlog-locale` cookie typo) was +opportunistically picked up in Fix-P because the touch was a single +character. + +Seven touch-disjoint Fix-J through Fix-P surfaces totalling 22 atomic +commits ran across `develop` between the W21 audit and the W22 +release-prep. Three sequenced merge windows preserved touch-disjoint +diffs without conflict (Fix-O before Fix-N on the cadence route; +Fix-L before Fix-N on the onboarding components; Fix-M before Fix-O +on the PR worker test). No file had two agents editing the same lines. + +## Strategic posture + +### v1.5 iOS readiness + +The iOS sprint has the contracts it needs to start Day 1. Locked +server contracts after v1.4.25: + +- `POST /api/measurements/batch` — Apple Health bulk ingest with + `inserted | duplicate | skipped` per-entry envelope, idempotency + wrapper, rate-limited (v1.4.23, unchanged here). +- `DELETE /api/measurements/by-external-ids` — HealthKit deletion-sync + (new in v1.4.25). +- `POST /api/workouts/batch` — HKWorkout-aligned typed ingest with + 20-sport-type union + GeoJSON LineString routes (new in v1.4.25). +- `POST /api/devices` — APNs token registration with paired + `apnsEnvironment` (v1.4.23, unchanged). +- `POST /api/onboarding/step` — wizard progression with rate limit + + audit + 409-on-concurrent (new in v1.4.25). +- `GET /api/personal-records?metricType=X` — paginated PR reads. +- `User.sourcePriorityJson` two-axis resolver with + `pickCanonicalSource()` — iOS sets Apple Health as the cumulative + + sleep + HRV + RHR favourite; Withings stays the point-measurement + default. +- `Measurement.deviceType` enum with iOS-17/18 HK long-tail mappings + ready to extend. +- `MedicationInventoryItem` — pen/vial 30-day clock available for an + iOS pen-scanner integration. +- `Workout` + `WorkoutRoute` tables (the table-side; endpoint signature + finalised during the iOS sprint). +- `docs/api/openapi.yaml` regenerates byte-identically from the Zod + registry; the drift gate is now hard-fail. + +### MDR boundary preservation + +The GLP-1 Research-Mode surface ships with three concentric defences: +chart gating (`User.researchModeAck*` columns require an active +acknowledgment before render), dialog acknowledgment (cites EU MDR +2017/745, MDCG 2021-24, EMA EPAR and Schneck / Urva 2024 before the +user can opt in; version-bump re-prompts), and Coach refusal at the +prompt layer (GROUND RULE 15 across all six locales, refusal-probe +matrix in CI). The titration ladder display is framed as +reference-not-advice and bound by the same GROUND RULE 15. Drug-level +chart-side 90-day staleness wiring stayed deferred for v1.4.26 (the +helper module shipped tested in W19c-Safety but the chart conditional +isn't wired — the unused helper was deleted during Fix-O after the +reconcile pass; the 90-day gate becomes a clean v1.4.26 surface when +the maintainer decides to wire it). + +### Branch model + +`develop` is the daily target; `main` is release-only and follows tags. +v1.4.25 is the sixth tag through the develop-to-main release-merge model +introduced in v1.4.20 F1. GHCR auto-deploy fires only on `main`; the +explicit `vars.COOLIFY_AUTO_DEPLOY=true|false` toggle closes the +silent-miss pattern that hit v1.4.21 / v1.4.22 / v1.4.23. + +## 0-10 score per area + +| Area | Score | Note | +|---|---|---| +| Insights | 9 | Seven-sub-page split lands cleanly; Targets redesign with conditional Coach-handoff is the tightest the layout has been; mother page slims to general status + Coach hero. Half-point off for the duplicate `StatusCard` regression that needed a Fix-I hotfix mid-release. | +| Dashboard | 9 | Top-tile polish (single-line headings, inline trend arrow, baseline alignment across tiles) closes the v1.4.24 reviewer note; comparison-overlay default removed in favour of per-chart preference; VO2 max secondary-metric tile lands as opt-in. | +| Coach | 9 | Native per-locale prompts replace the REPLY LANGUAGE footer across six locales; GROUND RULE 9 + GROUND RULE 15 close the MDR boundary at the prompt layer; 1800+ refusal-probe assertions in CI on every push. Half-point off for FR/ES/IT/PL prose still being LLM-drafted (structural matrix covers safety; the prose itself benefits from a native human pass — queued for v1.4.26). | +| Settings | 8 | Source-priority screen ships with drag-reorder + per-device override + audit-log; doctor-report per-section toggles + mood-default-off lands; Research-Mode toggle with version re-prompt banner lands. Single point off for the settings-cog vs per-message-controls debate (v1.4.23 carryover) still unresolved. | +| Admin | 8 | Login overview gains location + provider columns + CSV polish; coach-feedback header layout shift closed; notification-status-card icon parity restored; mobile admin nav gains Overview link. Two points off for the seven orphan endpoint go / no-go decisions still pending a maintainer call. | +| Mobile (web) | 8 | 44-px touch-target floor across onboarding wizard, top-bar, section strips and injection-site picker hits WCAG 2.5.5; dashboard tile baseline alignment regression-pinned. Two points off because the e2e workflow needed the Fix-I hot-fix re-anchor on dashboard insight-text + chart-tick locators (now class-independent). | +| Tests | 9 | 2 244 → 3 828 unit (+1 584); 140 → ~170 integration; new drift-guards across Apple Health mapping, side-effect taxonomy triangle, sleep-stage chart i18n, Health-Score provenance, safety-contract YAML matrix, plus per-user TZ regressions and the 1800-assertion refusal-probe matrix. Half-point off for the two pre-existing `coach-prefs.test.ts` `NextRequest` URL mock failures still carried from v1.4.23. | +| CI | 9 | OpenAPI drift gate flips to hard-fail; refusal-probe matrix runs on every push; multi-arch GHCR builds clean after the Fix-G/H lowercase fix-up; explicit `vars.COOLIFY_AUTO_DEPLOY` toggle closes the silent-miss pattern. Half-point off for the Fix-H lowercase regression that needed catching post-PR-open. | +| Docs | 9 | CHANGELOG expand in W22 brings the public artifact in lockstep with what shipped; per-section delta is complete; deferred-to-v1.4.26 / v1.5 sections are accurate. Half-point off because the W22 reconcile was the C-1 Critical of the release and only ran after the W21 product-lead caught it. | +| Repo hygiene | 9 | 380 dead i18n keys dropped; dead prompt constants removed; `` + `` + `` + orphan query keys + `/api/insights/general-status` orphan all gone; `MedicationDetailSection` wrapper + shared route-guards / read-error / dose-string / research-mode-types modules collapse 11+ duplications. Half-point off for the 148 remaining dead i18n keys queued for a v1.4.26 second-pass. | +| Demo readiness | 8 | Multi-arch image is live so the demo VPS pulls native arm64; W11b demo-deploy report covers the runbook; OpenAPI spec is authoritative for iOS DTO codegen. Two points off because the demo seeding still depends on the manual host-side fallback when Coolify auto-deploy is toggled off, and because the iOS Swift app itself is queued for v1.5 (the demo is web-only until then). | + +## v1.4.26 backlog + +24 deferred Mediums + 40 Lows + maintainer-decision items split across +the following themes: + +- `User.onboardingGoals` schema column + server-side persistence + (today the goals chip-picker holds the choice client-side only; + the dashboard-widgets seed feature reads from it in v1.4.26). +- `advance()` hook extraction across the three onboarding step + components (simplifier surfaced; deferred to avoid collision with + the v1.4.25 touch-target pass). +- 148 dead i18n keys remaining after the W15 380-key sweep (need a + second-pass call-site verification). +- Drug-level chart-side 90-day staleness clock wiring (acknowledgment + dialog already re-prompts on version bump and Coach refuses at + GROUND RULE 15; the clock is defence-in-depth). +- FR / ES / IT / PL Coach + insights prose hand-review by native + speakers (structural refusal-probe matrix covers safety; the user- + facing prose benefits from a human pass). +- Sleep sub-page stacked-column visual polish (stage colours + legend + density). +- VO2 max chart-row card on `/insights/` (dashboard tile + shipped; chart card queued alongside the iOS body-composition page). +- Mood verbal-labels persistence behind a per-user toggle. +- Coach `lastYear` baseline + row-tap-to-prefill polish. +- Lazy-loaded locale JSON bundles (all six locales import + synchronously today, ~675 KB to every client). +- iOS-18 long-tail HK identifier mappings (sleep apnea, GAD-7 / + PHQ-9, running form, paddle / row / ski distances, pregnancy / + cycle, FHIR clinical). +- Seven orphan endpoint go / no-go decisions + (`/api/admin/ai-settings`, `/api/admin/backup/test`, + `/api/admin/status-overview`, + `/api/monitoring/{glitchtip,umami}/test`). +- `glp1-pk.ts` and `glp1-knowledge.ts` unused-export decisions + (internalise vs wire dashboard chip). +- Pearson incomplete-beta replacement (v1.4.23 carryover already + partially closed with the conservative MIN_PAIRED_N raise — the + rigorous replacement is the v1.4.26 candidate before correlation + auto-discovery widens in v1.5/v1.6). + +## Carry-overs + +- The two pre-existing `coach-prefs.test.ts` `NextRequest` URL mock + failures still carry through from v1.4.23. Tracked in + `.planning/v1426-backlog.md`; non-blocking for the release. +- Manual-workout route `findFirst` serial pattern stays in place + (no manual workout UI today; contract correctness only matters + once v1.5 UI lands). +- The Coolify auto-deploy MCP API path still fires + `force=true` and reports "finished" in ~18 seconds without a real + `docker pull`, but the new `vars.COOLIFY_AUTO_DEPLOY` toggle plus + the "Watch image registry for new digests" UI checkbox capture the + durable fix; the host-side retag fallback recipe lives in + `.planning/coolify-auto-deploy-howto.md` for the cases where the + toggle is off. + +## Strategic next + +The v1.5 sprint starts with iOS first launch (login + dashboard + +widget) against locked server contracts — pure Swift work, zero +server-side touches. P2 (Apple Health sync) calls the v1.4.23 +`POST /api/measurements/batch` against the v1.4.25 device-type + +two-axis resolver. P3 (Coach extended for HRV / sleep / resting HR / +steps) ratchets `PROMPT_VERSION` 4.25.0 → 5.0.0 and copies the web +`` UX to iOS. P4 (per-metric APNs alerts) leans on the +v1.4.23 APNs scaffolding for the send-side. P5 (workouts + GeoJSON +routes) calls the v1.4.25 `POST /api/workouts/batch` against the +shipped Workout + WorkoutRoute tables.