diff --git a/.planning/ROADMAP.md b/.planning/ROADMAP.md index 9253c12ec..8e19fa7e3 100644 --- a/.planning/ROADMAP.md +++ b/.planning/ROADMAP.md @@ -1,270 +1,124 @@ -# HealthLog v1.4.24 — roadmap +# HealthLog — roadmap -Milestone: **v1.4.24 — pre-iOS final polish** (not yet kicked off) -Latest tag at start: v1.4.23 (live in prod) +Current line: **v1.9.0** (shipping — Insights time-ranges, deeper mood, medication +drug-coding into FHIR, advanced-settings rebuild, connection-pool stability). +Trunk: `main`, always releasable. Release model in CLAUDE.md. -Carry-over candidates already captured in -`.planning/v1423-backlog.md`: - -- Pearson incomplete-beta replacement — v1.4.23 W5 H6 raised - `MIN_PAIRED_N` from 14 → 20 as a conservative surfacing-gate - patch. Land the rigorous incomplete-beta or normal-approx - replacement before v1.5/v1.6 auto-correlation discovery ships. -- OpenAPI drift gate flip from warn-only to hard-fail — generator - covers ~880 lines vs the legacy hand-maintained spec's 5 468; - complete registry coverage in parallel so the lockstep happens - before the iOS DTO codegen is downloading the spec - automatically. -- Settings-cog vs per-message-controls UX consolidation — Design - pushback during W6 raised concern that the dual surface - duplicates intent. Wait on first-week thumbs data, decide - whether per-user prompt prefs drift from per-message ratings. -- `coach-prefs.test.ts` integration `NextRequest` URL mock - regression — pre-existing failure that predates v1.4.23. - Investigate the harness's `cookies()` mock interaction with - `NextRequest`'s URL parsing. -- Sec-MED-1 + LOW cluster — intra-batch dedup accounting - (Sec-LOW-1), idempotency 422 retry hint in the OpenAPI 422 - description (Sec-LOW-2), APNs key-file path redaction in - wide-event meta (Sec-LOW-3), refresh-token failure audit - `userId` extension (Sec-LOW-4). -- S-05 simplify deferral (Session B) — touched too many call - sites for a single reconcile commit. - -Reserved next strategic milestones: - -- **v1.5** — iOS app + Apple Health integration + per-metric APNs - alerts. Refreshed strategic plan with concrete file paths at - `.planning/phase-W6-v1423-product-lead-review.md`. P1 is now a - ~5-day iOS Swift sprint because every server-side contract - landed in v1.4.23. Per-user timezone foundation - (`.planning/feature-user-timezone.md`, issue #167) is now scoped - into v1.4.25 — Marc directive 2026-05-14, packs the iOS-launch - prep into the v1.4.25 polish window instead of slipping it. -- **v1.6+** — Auto-correlation discovery (FDR-controlled), Coach - full-page route at `/insights/coach`, conversation-driven goal - setting. +Next milestone: **v1.10.0 — derived / synthesized wellness metrics + provenance + docs**. --- -## Previous milestone — v1.4.23 (completed 2026-05-11) +## v1.10.0 — derived metrics, provenance, docs, discoverability -| Wave | Goal | State | -| ---- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----- | -| 1 | Research — Apple Health type mapping, APNs library decision (`@parse/node-apn`), OpenAPI tooling choice (`zod-openapi` + `yaml@^2`) | done | -| 2 | Apple Health foundation — F1 `MeasurementType` extension + sleep-stage enum + composite dedup index, F2 `APPLE_HEALTH` source + UI badge, F3 `POST /api/measurements/batch` + sleep-stage analytics aggregation | done | -| 3 | APNs scaffolding — F4 `@parse/node-apn` sender + dispatcher cascade rewire, `Device` columns + paired CHECK constraint, `POST /api/devices` apnsToken acceptance + cross-user-hijack guard | done | -| 4 | OpenAPI generator + Coach metric slot + native-auth hardening + Coolify runbook — F5 `zod-openapi` registry + warn-only CI gate, F6 nine HealthKit categories in strict schema + PROMPT_VERSION 4.23.0, F7 per-device refresh-token replay-detection + device-management endpoints, F8 Coolify runbook + `::notice::` line | done | -| 5 | Hygiene (H1-H7) — sentinel parser malformed-enum annotation, analytics chunked-paged aggregate, drawer controlled-prop refactor, per-user prompt prefs surface, `medication_schedules.days_of_week` deploy, Pearson n≥20 gate, Coach feedback observation view | done | -| 6 | Multi-agent QA + reconcile — six reviews (code, security, design, senior-dev, simplify, product-lead); 1 CRIT + 9 HIGH + 4 simplify + 3 MED applied; LOWs triaged into v1423-backlog | done | -| 7 | Release v1.4.23 — bump, CHANGELOG, release-merge, tag, GHCR, Coolify deploy, /api/version=1.4.23, smoke, GH release, docs + landing sync, brief | done | +The headline is derived wellness metrics that are honest on the data HealthLog +actually has — daily snapshots, not continuous streams. The governing constraint: +HRV is a single nightly SDNN value, there is no continuous HR, no EDA, no raw ECG/PPG, +and the nightly drain tombstones per-sample rows to a daily mean. So anything built on +intraday physiology is out of scope; day-scale deviation-from-personal-baseline +statistics are the sweet spot. Full synthesis in +`.planning/v1.10-derived-metrics-PROPOSAL.md`, implementation plan in +`.planning/v1.9.1-derived-metrics-IMPLEMENTATION-PLAN.md` (targeted at v1.10.0, not +1.9.1), research reports under `.planning/v1.10-research/`. -Milestone completed 2026-05-11 — v1.4.23 LIVE in prod. -Release brief: `docs/audit/v1423-summary.md`. -Backlog seeded to `.planning/v1423-backlog.md`. v1.5 strategic plan refresh -at `.planning/phase-W6-v1423-product-lead-review.md`. +### Ordering (data-availability first) + +A derived metric appears only when its required inputs exist; composites degrade +gracefully and never present a headline number secretly computed from one of N signals. +Precedent: the Personal Health Score null-redistributes missing pillars. +(`.planning/v1.10-research/DESIGN-PRINCIPLE-data-availability.md`.) + +1. **Per-metric Vitals dashboard** (flagship) — each available signal shows its own + personal-baseline card; absent signals don't render, so it scales 1→30 metrics with + no data assumptions. Pure rolling stats over inputs we 100% have, plus a multi-signal + early-strain flag. Build this first. +2. **Pre-computed easy wins** — age/sex-adjusted reference ranges (cross-cutting + enabler: `dateOfBirth` / `gender` are stored but unused for norms), Fitness Age / + cardio band from VO2max, Vascular Age framing from PWV. Render only when the device + value exists. +3. **Composites** — Sleep Score and a Readiness / Wellness index (extending the Personal + Health Score), each with a minimum-inputs threshold, reweighting around missing + inputs, a visible coverage / confidence indicator, and a "track BP/HRV to sharpen + this" nudge. +4. **Broader correlation discovery** — expand the fixed-hypothesis correlation engine + into an FDR-controlled behaviour↔outcome discovery engine, surfacing only pairs with + enough paired n (the existing n≥20 gate pattern). + +Out of scope by construction (require sensing we deliberately don't retain): Body +Battery, all-day / real-time Stress, Strain / Training Load, a faithful Recovery score, +AFib / ECG derivation, breathing-disturbance / AHI, Oura Resilience, minute-by-minute +time-in-zone. State this plainly so we never over-promise; surface a device-computed +value as a passthrough where one exists, never re-derive it. + +### Provenance / transparency (acceptance criterion, not an afterthought) + +Every derived metric openly states what it's based on and links the basis: +- exact inputs + method / formula (plain-language + the math), +- why this method (rationale), +- a citation / link to the underlying authority (IEEE / RFC / WHO / LOINC / + peer-reviewed studies / Wikipedia — whichever is right), +- confidence / limitations stated honestly (SDNN≠RMSSD, snapshot-not-continuous, + consumer-sensor validity caveats), +- rendered through the existing collapsible provenance / explainer pattern — no + black-box number. + +Needs a shared provenance / citation data model so each derived metric carries its +sources, surfaced via the explainer component everywhere it appears. +(`.planning/v1.10-research/REQUIREMENT-docs-and-provenance.md`.) + +### Documentation audit + continuation + +- Audit the docs site (`healthlog-docs`), in-repo `docs/`, README, and the OpenAPI + contract for accuracy vs the shipped state — much drifted across the v1.8.x → v1.9.0 + arc. +- Continue / extend docs to cover the new derived metrics with the same provenance: + meaning, computation, linked standard, required data, graceful-degradation behaviour. +- This `ROADMAP.md` / `STATE.md` refresh folds into the audit. + +### Discoverability / growth + +A discoverability / growth workstream stands alongside the milestone (scope to be +shaped during planning). --- -## Previous milestone — v1.4.22 (completed 2026-05-10T22:43:50+02:00) +## Standing deferred items -| Wave | Goal | State | -| ---- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ----- | -| 1 | Research + probe — Playwright PROD probe (BD-Zielbereich 5th attempt, api-tokens 5th attempt, metric token leaks, Targets page brainstorm); health-coach prompt research | done | -| 2 | Insights surface polish — A1 BD framing, A2 BD-Kachel parity, A3 comparison-toggle global, A4 grid normalisation, A5 Muster rename + tabs above hero, A6 token-leak fix | done | -| 3 | Coach polish — B1 prompt rewrite (PROMPT_VERSION 4.20.2 → 4.22.0), B2 collapsible evidence, B3 Gravatar parity, B4 disclaimer move, B5 settings cog removal | done | -| 4 | Other surfaces + backlog cleanup — C1 Zielwerte sparkline + Δ-vs-last-month, C2 api-tokens 5th attempt, C3 Coolify auto-deploy runbook, C4 AuthShell flicker → proxy.ts, C5 node-26-alpine deferred, D backlog wave (12 items) | done | -| 5 | Multi-agent QA + Product-Lead — code, security, design, senior-dev, simplify, product-lead; 0 CRIT, 7 HIGH all applied inline, ~6 MED applied, rest deferred | done | -| 6 | Release v1.4.22 — bump, CHANGELOG, release-merge, tag, GHCR, host-side retag deploy (Coolify secrets still missing), /api/version=1.4.22, smoke, GH release, docs+landing sync, brief | done | +Carried forward; pick up as they become relevant or fold into a milestone. -Milestone completed 2026-05-10T22:43:50+02:00 — v1.4.22 LIVE in prod. -Release brief: `docs/audit/v1422-summary.md`. -Image digest: -`sha256:865154614303fdc362ee3941776f73ec0f60e1f16112ec272a75cbbe28e2cffb`. -Backlog seeded to `.planning/v1422-backlog.md`. v1.5 strategic plan at -`.planning/phase-W5-v1422-product-lead-review.md`. +- **BYOK AI fallback** — resilience gap: no provider fallback is configured, so a single + provider outage stalls assessment generation. +- **`MedicationAdministration` per-export cap tuning** — the per-dose administration + emission added in v1.9.0 needs a sensible cap for high-volume histories. +- **SNOMED route / site upgrade** — route-of-administration + injection-site could carry + SNOMED codes rather than the current local enum. +- **German bfarm ATC URI behind a locale toggle** — the export codes ATC against the WHO + system; a German deployment may prefer the bfarm URI. --- -## Previous patch — v1.4.21 (completed 2026-05-10T17:46+00:00) +## Shipped (coarse — CHANGELOG.md is the record) -| Phase | Goal | State | -| ----- | ------------------------------------------------------------------------------------------------------------------------------------------------------ | ----- | -| F | Same-day patch on top of v1.4.20 — Daily Briefing regenerate fix, Coach day-level snapshot, duplicate streaming bubble, drawer header, scope picker UI | done | -| E | Release v1.4.21 — develop → main release-merge, bump, CHANGELOG, tag, GHCR, host-side retag fallback, /api/version=1.4.21, smoke, GH release, brief | done | - -Patch completed 2026-05-10T17:46+00:00 — v1.4.21 LIVE in prod. -Release brief: `docs/audit/v1421-summary.md`. -Image digest: -`sha256:4e818d44702c3581a14d6480a953fd20d16cbbaf21c41e0c778c07340d3c4b1c`. - ---- - -## Previous milestone — v1.4.20 (completed 2026-05-10T16:49:25Z) - -| Phase | Goal | State | -| ----- | ----------------------------------------------------------------------------------------------------------------------------------------------------------- | ----- | -| F0 | Bootstrap — commit dangling v1.4.19 reports, scaffold STATE+ROADMAP | done | -| F1 | Branch model — long-lived `develop` from `main` HEAD; GHCR builds on `main` + `v*` only | done | -| F2 | Document branch + release model — CONTRIBUTING.md + docs site mirror page | done | -| FX | User-facing artifact cleanup — PII (no real names, no health figures), internal jargon, German leaks; CHANGELOG + docs/audit + GH releases + docs + landing | done | -| F5 | Best-practice GitHub repo audit — CODE_OF_CONDUCT.md, issue + PR templates, dependabot expansion, package.json metadata | done | -| F6 | Multi-agent QA on new docs — 38 docs pages cross-checked vs deployed state, CRIT + HIGH inline | done | -| B1 | Hero strip + Daily Briefing + Suggested-prompts | done | -| B2 | AI Coach drawer + SSE streaming + encrypted persistence | done | -| B3 | Correlation discovery + Trends row with AI annotations | done | -| B4 | Weekly Report + Storyboard markers + Mobile passes | done | -| B5 | Personal Health Score (composite 0–100, 3 bands) | done | -| D | Multi-agent QA — code, security, design, senior-dev, simplify, product-lead; 13 HIGH + 6 MED + 5 simplify-yes inline; 22 MED + 16 LOW deferred | done | -| E | Release v1.4.20 — develop → main release-merge, bump, CHANGELOG, tag, GHCR, host-side retag deploy fallback, /api/version=1.4.20, smoke, docs+landing sync | done | - -Milestone completed 2026-05-10T16:49:25Z — v1.4.20 LIVE in prod. -Release brief: `docs/audit/v1420-summary.md`. Backlog seeded to -`.planning/v1421-backlog.md`. v1.5 strategic plan at -`.planning/phase-D-v1420-product-lead-review.md`. - ---- - -## Previous milestone — v1.4.19 (completed 2026-05-10T12:39:59Z) - -| Phase | Goal | State | -| ----- | -------------------------------------------------------------------------------------------------------------------------------------------------- | ----- | -| 0 | Bootstrap — STATE+ROADMAP for v1.4.19 | done | -| A1 | BD-Zielbereich constant 50% (4th attempt) — live-DB root-cause + integration test | done | -| A2 | Charts mobile audit — universal X-tick density helper, mobile-first stacked headers across HealthChart / MoodChart / MedicationComplianceChart | done | -| A3 | `/insights` polish — Comparison-toggle relocated, single page-level refresh, BP/Weight tile strip removed, raw-token leak guarded | done | -| A4 | AI prompt — GROUND RULE 7 forbids default-positivity opener, PROMPT_VERSION 4.16.1 → 4.19.0 | done | -| A5 | Settings/Integrations status-UI — `` consolidates Withings + Mood Log, divider parity, locale-aware relative-time | done | -| A6 | Settings mobile audit — input heights equalised at 36 px, action buttons standardised, Sprache select hoisted to its own row | done | -| A7 | Admin polish — feedback tab strip scrollbar, api-tokens 4th-attempt truncate+tooltip, Einklappen toggle removed, Zielwerte i18n | done | -| A8 | Quality-of-life audit — 78 findings prioritised CRITICAL / HIGH / MED / LOW | done | -| B | Apply A8 — 6/6 CRITICAL + 21/25 HIGH inline; 31 MED + 16 LOW carried to v1.4.20 backlog | done | -| D | Multi-agent QA (5 reviewers) + Product-Lead → v1.5 redesign plan filed | done | -| E1-E3 | Release v1.4.19 — tag, GHCR build green, host-side retag deploy via fallback (Coolify deploy hung, retried via SSH), GH release, docs+landing sync | done | - -Milestone completed 2026-05-10T12:39:59Z — v1.4.19 LIVE in prod. -Release brief: `docs/audit/v1419-summary.md`. Backlog seeded to -`.planning/v1420-backlog.md` (carry-over) and v1.5 strategic items in -`.planning/v15-backlog.md` + `.planning/phase-D-v1419-product-lead-review.md`. - ---- - -## Previous milestone — v1.4.18 (completed 2026-05-10T11:45+02:00) - -| Phase | Goal | State | -| ----- | ------------------------------------------------------------------------------------------------------------ | ----- | -| 0 | Bootstrap — STATE+ROADMAP for v1.4.18 | done | -| A1 | BD-Zielbereich tile — 7T/30T sub-values render real numbers (currently "—" even with data) | done | -| A2 | `/admin/api-tokens` table scrollbar (3rd attempt) — Playwright live-verify against prod, fix actual offender | done | -| A3 | Chart visual revert + per-chart toggles — drop gradient/emoji/auto-mean, ship 3 opt-in overlay toggles | done | -| B1 | Achievements expansion — research + 15-25 new, hidden Easter-eggs, lock filter, hidden cards | done | -| D | Multi-agent QA + Product-Lead — code-reviewer, security, design, senior, simplify, product | done | -| E | Release v1.4.18 — bump, CHANGELOG, tag, GHCR, deploy, /api/version=1.4.18, smoke, docs+landing sync | done | - -Marathon completed 2026-05-10T11:45+02:00 — v1.4.18 LIVE in prod. -Full report: `docs/audit/v1418-summary.md`. Backlog seeded to -`.planning/v1419-backlog.md` (tactical) and `.planning/v15-backlog.md` - -- `.planning/phase-D-v1418-product-lead-review.md` (strategic). - ---- - -## Previous milestone — v1.4.17 hotfix (live 2026-05-10T07:58+00:00) - -| Phase | Goal | State | -| ------ | ---------------------------------------------------------------------------------------------------------- | ----- | -| Hotfix | `/insights` TypeError on legacy cached blob — `isLegacyInsightPayload()` flag + advisor card short-circuit | done | -| Audit | `git grep -nE '\.replace\(' src/` — 82 hits scanned, crash site is the only fragile case | done | -| E | Release v1.4.17 — bump, tag, GHCR, host-side retag deploy, GH release | done | - -Hotfix completed 2026-05-10T07:58+00:00 — v1.4.17 LIVE in prod. -Detailed report: `.planning/phase-v1417-hotfix-report.md`. - ---- - -## Previous milestone — v1.4.16 (completed 2026-05-10T04:05+02:00) - -| Phase | Goal | State | -| ----- | ----------------------------------------------------------------------------------------------------- | ----- | -| 0 | Bootstrap — STATE+ROADMAP for v1.4.16 | done | -| A1 | Sidebar admin-expand bug — Admin nav from non-admin route + Gravatar dropdown side-effects | done | -| A2 | BD-Zielbereich real-fix (regression from v1.4.15 A4) — root-cause + E2E | done | -| A3 | `/admin/api-tokens` table responsive — card-list mobile fallback | done | -| A4 | "7-Tage-Schnitt" → "7-Tage-Trend" DE + indicator on ALL charts | done | -| A5 | Top-tile-selector real-fix — widget-id enum drift fixed | done | -| A6 | Medication-chart 7d-trend + target-range — match other charts | done | -| A7 | AI Generator rate-limit 10/h + cache-invalidate-on-new | done | -| A8 | Umlaute encoding bug + login-overview UTF-8 audit + long-window split-half delta | done | -| B1a | Charts visual leap — gradients, baseline, rich tooltip, mood emoji glyphs | done | -| B1b | Insights surface visual leap — page hero, recs grid, summary typography, dashboard preview | done | -| B2 | AI provider settings UX cleanup — single pulldown-driven form, fallback chain editor | done | -| B3 | Admin System-Status host-load chart — CPU/memory/disk-io graph last 2h | done | -| B4 | Admin logs visibility deepening — filterable audit log + structured wide-event tail | done | -| B5a | Medical-reference grounding — AHA / ESC / ESH / WHO bundle + UI citations | done | -| B5b | Multi-provider redundancy — try-each-on-hard-failure, configurable fallback order | done | -| B5c | Per-recommendation explainability — WHY / WINDOW / CITATIONS card with mini-chart | done | -| B5d | Confidence score per recommendation — deterministic 0-100 from sufficiency / recency / signal | done | -| B5e | User-feedback loop — thumbs persisted to RecommendationFeedback + daily aggregator | done | -| B6 | Settings naming-audit — stringency, consistency, no double naming | done | -| B7 | Settings → Export menu (Arztbrief consolidated) — new `/settings/export` route | done | -| B8 | Extended comparison views — Vormonat / Vorjahr overlay across charts + tiles + insights | done | -| C | Catch-up — 5 of 8 deferred HIGH + 3 of 5 mobile MED + docker-publish drop qemu-arm64 | done | -| D | Multi-agent QA + Product-Lead — code-review, security, design, senior, simplify, product-lead | done | -| E1-E3 | Release v1.4.16 — bump, tag, GHCR (both green), host-side retag deploy, GH release, docs+landing sync | done | - -Marathon completed 2026-05-10T04:05+02:00 — v1.4.16 LIVE in prod. -Full report: `docs/audit/v1416-summary.md`. v1.5 backlog seeded at -`.planning/v15-backlog.md`. - ---- - -## Previous milestone — v1.4.15 (completed 2026-05-09T22:50+02:00) - -| Phase | Goal | State | -| -------- | ------------------------------------------------------------------------------------------------------------ | ----- | -| 0 | Bootstrap — STATE+ROADMAP for v1.4.15 | done | -| A1 | Nav conditionals + sidebar context-awareness | done | -| A2 | `/admin` overview redesign + `/admin/api-tokens` responsive | done | -| A3 | Quick-add labels + Stimmung-Card mobile + onboarding flicker | done | -| A4 | Dashboard analytics fixes (BD-Zielbereich, Medikamente graph, Stimmung agg, 7-Tage-Trend, top-tile selector) | done | -| A5 | Mobile UX audit + chart scroll-lockup fix | done | -| B-mobile | Mobile audit fix-application (5 commits, 2/2 CRITICAL, 6/8 HIGH) | done | -| B1 | Backup completeness — restore, download, upload, audit, docs | done | -| B2 | Withings + moodLog sync robustness — status UI, refresh rotation, telegram on fail | done | -| B3 | Notification reliability — auto-disable on 410, exp backoff, status visible | done | -| B4 | Achievements UI — `/achievements` page + dashboard card | done | -| B5 | Onboarding tour first-run — spotlight, Esc/arrows, restart from Settings | done | -| B6 | Doctor-report v2 — configurable date range, practice-name on cover | done | -| C1 | AI/Codex hardening — provider abstraction, JSON schema, citations, scope, slug-drift defense | done | -| C2 | Auto-deployment — Coolify webhook on GHCR push, failure-notify | done | -| C3 | CI/e2e reliability audit — root-cause flaky specs, docker-publish cache fix | done | -| C4 | i18n coverage audit — EN+DE parity, non-empty test guards | done | -| C5 | Empty-states audit — 13 surfaces upgraded | done | -| D | Multi-agent QA (5 reviewers + reconcile) | done | -| E1–E3 | Release v1.4.15 — bump, tag, GHCR, host-side retag deploy, docs+landing sync, Marc-Brief | done | - -Marathon completed 2026-05-09T22:50+02:00 — v1.4.15 LIVE in prod. -Full report: `docs/audit/v1415-summary.md`. - ---- - -## Previous milestone — v1.5 (relabelled v1.4.14, completed 2026-05-09) - -| Phase | Goal | State | -| ----- | -------------------------------------------------------------------------------------------------------------------------------------- | ----- | -| 0 | Bootstrap — STATE+ROADMAP, git/CI/prod sanity, single chore commit | done | -| 1 | Verify Codex-OAuth end-to-end (device-start shape, prod logs, gpt-5.3-codex slug) | done | -| 2 | v1.4.6 deferred backlog — T2.1..T2.7 (wipe scope, DST math, 5xx → 422/503, status-card test, redact regex, Backups view, /admin/users) | done | -| 3 | End-to-end test coverage — authed dashboard, add-measurement, KI flow, doctor PDF, insights, mobile smoke, axe-core extended | done | -| 4 | Performance audit — Playwright capture, `docs/audit/v15-performance.md`, top-3 wins inline | done | -| 4b | Admin Panel refactor — `/admin/[section]` dynamic routes mirror Settings; sidebar Admin group; legacy hash redirects; bundle-size win | done | -| 5 | UX polish — design-review top-10 friction points, CRITICAL/HIGH triage | done | -| 6 | Multi-agent QA (parallel) — code-reviewer, security, visual/UX, simplify | done | -| 7 | Pre-release verification — typecheck / lint / format / test / integration / build / e2e | done | -| 8 | Release v1.4.14 — rebrand patch deploy (host-side retag, GHCR `:1.4.14`, image digest `0ced46004a54…`) | done | -| 9 | Docs + landing site sync to v1.4.14 | done | -| 10 | Backfill GitHub releases v1.4.7..v1.4.13 from CHANGELOG | done | -| 11 | Final summary `docs/audit/v1414-summary.md` with Marc-Brief | done | - -Marathon completed 2026-05-09T18:35+02:00 — v1.4.14 LIVE in prod. -Full report: `docs/audit/v1414-summary.md`. +- **v1.9.0** (2026-06-02) — Insights time-ranges + period-over-period deltas, deeper + mood insights, medication ATC + RxNorm into the FHIR export (+ MedicationAdministration + per dose, Coverage-from-bare-KVNR, Composition narrative), advanced-settings rebuild, + inline targets (`/targets` retired), connection-pool sizing fix, decoupled insight + warm. Migration `0103`. +- **v1.8.7.1** (2026-06-02) — assessment on every HealthKit metric page, one-tap + pre-generation. +- **v1.8.x** (2026-05-31 → 06-02) — Insights big release (reliable assessments, graded + compression, embedded targets, explainers, English slugs), compliance + intake-slot + medical fixes, injection-site tracking, FHIR codesystem + Coverage, instant + assessments, GLP-1 blood-level chart. +- **v1.7.x** (2026-05-31) — health-record PDF + FHIR R4 export, PRN + cyclic schedules, + unified dashboard snapshot + nightly insight pre-gen, offline sync delta feed, full + HealthKit chart coverage, Coach data clustering. +- **v1.6.0** (2026-05-30) — medication editor overhaul + route of administration, + one-time injection, today-tile read-flip, profile-photo upload. +- **v1.5.x** (2026-05-24 → 29) — native iOS client public beta + Apple Health sync, + medication scheduling (RRULE / rolling / one-shot), step consolidation, `safeFetch` + egress hardening, avatar storage, `SESSION_COOKIE_SECURE` through compose. +- **v1.4.24 → v1.4.50** (2026-05-11 → 24) — rollup tiers + perf, APNs + `push_attempts`, + reminder suppression, self-healing stale-shell, localisation, MoodLog reverse-sync. +- **v1.4.23** (2026-05-11) — pre-iOS backend foundation + hygiene (Apple Health enum + + batch ingest + APNs scaffolding + OpenAPI generator). Earlier history archived. + diff --git a/.planning/STATE.md b/.planning/STATE.md index d4dff4c10..8f8fdb82a 100644 --- a/.planning/STATE.md +++ b/.planning/STATE.md @@ -1,245 +1,117 @@ -# v1.4.23 marathon — state log +# State log — where we are now -Status: SHIPPED — v1.4.23 LIVE in prod -Last update: 2026-05-11 (release wave) +Status: **v1.9.0 shipping** — on branch `release/v1.9.0`, Draft PR #240 → `main`, +about to tag + docker-publish + deploy. +Last update: 2026-06-02 -> Previous milestone: v1.4.22 live (image digest -> `sha256:865154614303…`, `/api/version=1.4.22`). -> v1.4.23 = pre-iOS prep + hygiene. Backend foundation so the iOS -> Health app can ship in v1.5 without contract churn, plus 7 -> hygiene items from `.planning/v1422-backlog.md`. +> Single trunk: `main` is always releasable. `develop` is retired (archived as the +> tag `archive/develop-pre-v1.5`; never recreate it). Releases cut on a short-lived +> `release/vX.Y.Z` branch → PR → merge → tag → `docker-publish.yml` → manual deploy → +> verify `/api/version` on every target. See CLAUDE.md for the full model. -## Wave 1 — Research (single agent, three streams) +--- -- [ ] Apple Health `HKQuantityTypeIdentifier` → `MeasurementType` - mapping, with units + canonical aggregation rules -- [ ] APNs Node library decision (`apn` vs `@parse/node-apn` vs - raw HTTP/2) with maintenance / type-safety / footprint pros/cons -- [ ] OpenAPI 3.1 generator tooling for Next.js routes - (`@asteasolutions/zod-to-openapi`, `ts-rest`, `next-rest`, - hand-rolled) -- Detailed report: `.planning/phase-W1-v1423-research.md` +## v1.9.0 — what it carries -## Wave 2 — Apple Health foundation (F1+F2+F3) +Insights time-ranges + period-over-period deltas, deeper mood insights, medication +drug-coding into the FHIR export, an advanced-settings rebuild, an inline-targets +move, and a stability fix to the connection pool. -### F1 — `MeasurementType` enum extension +### Added -- [x] Added `HEART_RATE_VARIABILITY`, `RESTING_HEART_RATE`, - `ACTIVE_ENERGY_BURNED`, `FLIGHTS_CLIMBED`, - `WALKING_RUNNING_DISTANCE`, `VO2_MAX`, `BODY_TEMPERATURE` - (W1 recommended reusing `ACTIVITY_STEPS` + `SLEEP_DURATION` - verbatim; the iOS DTO already shipped `BODY_TEMPERATURE` so - that joined the additive set — net new = 7) -- [x] Prisma migration `0036_apple_health_measurement_types` -- [x] Sleep unit shifted from hours to minutes; new `SleepStage` - enum + nullable `Measurement.sleepStage` column (CHECK - constraint scopes it to SLEEP_DURATION rows) -- [x] Composite unique index `(user_id, type, source, external_id)` - — Apple Health batch dedup key -- [x] Unit conversions in - `src/lib/measurements/apple-health-mapping.ts` (16 unit tests) +- **Selectable time ranges on the Insights pages** — week / month / quarter / year + range pills per metric, with a period-over-period delta (the change in the average, + stated plainly with its direction). +- **Deeper mood insights** — time-of-day pattern (highest / lowest), a stability read, + and correlation cards against weight and blood pressure. Every correlation is + paired-n gated (only shown when there are enough paired days to mean anything). +- **Standard drug codes on a medication** — an optional ATC and RxNorm code (entered, + never guessed; migration `0103` adds the nullable `atc_code` / `rxnorm_code`). + These flow into the health-record export: the `MedicationStatement` codes the drug + with the WHO ATC system + RxNorm alongside the plain name, and the export adds a + `MedicationAdministration` for every dose actually taken or skipped. +- **`scripts/assert-deploy.ts`** — a deploy-verification script that checks a target + reports the expected version after a release. -### F2 — `MeasurementSource` enum expansion +### Changed -- [x] `APPLE_HEALTH` appended to the enum -- [x] UI badge in measurement-list (Dracula-pink chip) + bilingual - `measurements.sourceAppleHealth` keys + admin restore-route - enum guard sync +- **Medication advanced-settings page rebuilt** — import / intake-import / export in + their own group; the external-API endpoints listed one by one with collapsible + request examples; tidier layout. +- **Targets edited inline** — the standalone `/targets` page is retired; a metric's + target range is set from the metric itself. +- **Health-record export emits insurer Coverage from a bare KVNR** (not only with a + full insurer organisation present) and carries a top-level Composition narrative + summary. -### F3 — `POST /api/measurements/batch` +### Fixed -- [x] New route at `src/app/api/measurements/batch/route.ts`, - idempotent via `Idempotency-Key` -- [x] Validates batch ≤ 500 entries (returns 422 - `coach.batch.too_large`) -- [x] Dedupes via `(userId, type, source, externalId)` composite -- [x] Returns per-entry status (`inserted` / `duplicate` / - `skipped`) + idempotency-replay-safe -- [x] Integration test (`tests/integration/measurements-batch.test.ts`, - 6 cases including idempotency replay) -- [x] Sleep-stage aggregation in `/api/analytics`: - per-Berlin-day summary + `sleepStages` block - (`tests/integration/analytics-sleep-stages.test.ts`) -- Detailed report: `.planning/phase-W2-v1423-report.md` +- **App stays responsive under load** — `DB_CONNECTION_LIMIT` is now sized for real + concurrency (the 9 default could starve while a background insight warm and a + foreground request competed, surfacing as the self-recovering "server not + responding"); tunable for larger self-hosts. +- **Background insight warm decoupled + sync-burst invalidation debounced** — a warm + no longer blocks a page, and a burst of Apple Health / Withings sync no longer + triggers a storm of regenerations. +- **Medication card rows hold an equal height** ("last dose" / "next dose" alignment). +- **Glucose entered in mmol/L converts correctly** in the editor. +- **Scrollbar-gutter reserved** — admin + global layout no longer shifts when a + scrollbar appears. +- **Six legacy `*-status` routes documented** in the OpenAPI contract. -## Wave 3 — APNs scaffolding (F4) +--- -- [x] Library install + lock-file (`@parse/node-apn@8.1.0`, - W1 stream-2 decision) -- [x] `src/lib/notifications/senders/apns.ts` — `sendApnsPush()` - one-shot helper plus `sendViaApns(userId, payload)` dispatcher - entry mirroring the web-push contract -- [x] Env-var contract: `APNS_KEY_ID`, `APNS_TEAM_ID`, - `APNS_BUNDLE_ID`, one of `APNS_KEY` / `APNS_KEY_FILE`, - optional `APNS_PRODUCTION` — documented in `.env.example`, - all-or-none guard with one-shot warning when partially set -- [x] `Device` Prisma model extension via migration - `0037_apns_device_columns`: nullable `apnsToken` + - `apnsEnvironment`, paired CHECK constraint, single-column - index on `apns_token` for the dispatcher fan-out lookup -- [x] Dispatcher wiring: `APNS` joins the cascade as channel-type - 4; cascade order is now deterministic - (APNs → Telegram → ntfy → Web Push) -- [x] Mock provider at `src/lib/notifications/senders/__mocks__/apns.ts` - with queued per-token responses + recorded calls -- [x] Integration test - `tests/integration/apns-dispatch.test.ts` — round-trip, - hard-reject + cleanup, cascade fall-through (3 tests) -- [x] `POST /api/devices` accepts paired `apnsToken` + - `apnsEnvironment`, enforces hex format, applies the - cross-user-hijack guard at the APNs-token layer - (409 `apns_token_owned_by_other_user`) -- Detailed report: `.planning/phase-W3-v1423-report.md` +## What is live (the release arc since v1.4.23) -## Wave 4 — OpenAPI + Coach schema + native auth + Coolify +Coarse summary; CHANGELOG.md is the accurate per-release record. -### F5 — OpenAPI 3.1 generator + CI gate +- **v1.4.24 → v1.4.50** — perf + rollup tiers (measurement / mood / compliance / + cumulative-sum rollups, read-swap with live fallback, auto-converging boot + backfill), APNs delivery + per-channel test endpoint + `push_attempts` ledger, + server-side reminder suppression (`clientManaged`), self-healing stale-shell after + deploy, full localisation push, MoodLog reverse-sync. +- **v1.5.x** — native iOS client public beta + Apple Health sync (`POST /api/measurements/batch`, + per-day cumulative `stats:` overwrite), medication scheduling (RRULE / rolling / + one-shot lifecycle, creation wizard → modal-dialog compose-mode), per-day-cumulative + step consolidation, `safeFetch` egress hardening, self-hosted avatar storage, + `SESSION_COOKIE_SECURE` plumbed through compose. +- **v1.6.0** — medication editor overhaul + route of administration (`deliveryForm`, + migration `0088` ORAL backfill), one-time injection, today-tile read-flip onto the + canonical recurrence engine, profile-photo upload. +- **v1.7.x** — health-record export (enriched PDF + HL7 FHIR R4 bundle), PRN + cyclic + schedules, unified dashboard snapshot + nightly insight pre-generation, offline sync + delta feed (tombstones), full HealthKit chart coverage + display-unit preference, + Coach data clustering, multi-time compliance fixes. +- **v1.8.x** — Insights big release (reliable data-driven assessments, graded + compression, embedded targets, explainers, English slugs + tile-id aliases), + multi-time compliance, one-intake-row-per-dose-slot medical fix, injection-site + tracking, FHIR codesystem for HealthKit-only metrics, FHIR `Coverage`, instant + assessments (stale-while-revalidate), GLP-1 blood-level chart. +- **v1.8.7.1** — a plain-language assessment on every HealthKit metric page (~30 + metrics), one-tap pre-generation of every assessment. -- [x] Tool wired (W1 decision — `zod-openapi` (samchungy) + `yaml@^2`) -- [x] `pnpm openapi:generate` script emits - `docs/api/openapi.yaml` from Zod schemas -- [x] CI step compares generated vs committed; PR warns on drift - (`continue-on-error: true` — flips to hard-fail in v1.4.24+) -- [x] Legacy hand-maintained spec preserved at - `docs/api/openapi-v1422-legacy.yaml` during the incremental - migration +apps01 + demo are LIVE on the latest tag; v1.9.0 deploy is pending the tag. -### F6 — Coach + Daily Briefing schema slot for new metrics +--- -- [x] `aiInsightResponseSchema.dailyBriefing.keyFindings[].sourceMetric` - enum extends to include the 9 Apple Health categories - (hrv, sleep, resting_hr, steps, active_energy, flights, - distance, vo2_max, body_temp) -- [x] `trendAnnotations` schema mirrors the same additive enum -- [x] `coach/snapshot.ts` queries the new measurement types when - scope toggles them on; web-only accounts pay zero extra SQL -- [x] `CoachProvenance.metrics` + `counts` extended symmetrically -- [x] PROMPT_VERSION 4.22.0 → 4.23.0 with new GROUND RULE 12 - ("treat Apple Health categories as silent when absent") -- [x] EN + DE prompt bodies, OUTPUT FORMAT block, and i18n strings - all updated +## Deferred set (carry-forward from v1.9.0) -### F7 — Native API hardening (refresh-token per-device) +| Item | Where it goes | Note | +|---|---|---| +| Derived / synthesized wellness metrics | **v1.10.0** | per-metric Vitals dashboard first, then composites with coverage/confidence gating. Plans in `.planning/v1.10-derived-metrics-PROPOSAL.md` + `.planning/v1.9.1-derived-metrics-IMPLEMENTATION-PLAN.md` (targeted at v1.10.0) + `.planning/v1.10-research/*`. | +| Docs / provenance audit | **v1.10.0** | docs site + in-repo `docs/` + README + OpenAPI drifted across the v1.8.x → v1.9.0 arc; audit + continue, and surface every derived metric's sources via the explainer pattern. Requirement: `.planning/v1.10-research/REQUIREMENT-docs-and-provenance.md`. | +| Discoverability / growth workstream | **v1.10.0** | standing alongside the derived-metrics milestone. | +| BYOK AI fallback | open | resilience gap — no provider fallback is configured, so a single provider outage stalls assessment generation. | +| `MedicationAdministration` per-export cap tuning | open | the per-dose administration emission needs a sensible cap for high-volume histories. | +| SNOMED route / site upgrade | open | route-of-administration + injection-site could carry SNOMED codes rather than the current local enum. | +| German bfarm ATC URI behind a locale toggle | open | the export codes ATC against the WHO system; a German deployment may prefer the bfarm URI — gate behind a locale toggle. | -- [x] Refresh-token reuse-detection switches from "revoke all" to - per-device-token revocation (legacy null-deviceId tokens fall - back to user-wide revoke — safety hatch) -- [x] New route `GET /api/auth/me/devices` lists active devices - with last-seen, label, channels, isCurrent marker -- [x] New route `DELETE /api/auth/me/devices/[id]` revokes one device - (refresh + access tokens + Device row) -- [x] Alternate `DELETE /api/devices/[id]` for the iOS token-rotation - cleanup path (mirror of the auth/me variant) -- [x] Ownership-boundary 404 on cross-user attempts +--- -### F8 — Coolify auto-deploy fix for real +## Next milestone — v1.10.0 -- [x] `COOLIFY_WEBHOOK` + `COOLIFY_TOKEN` GitHub repo secrets - documented (maintainer action; see - `.planning/coolify-auto-deploy-howto.md` for the verbatim - recipe) -- [x] Coolify "Watch image registry for new digests" UI toggle - documented as the load-bearing piece (maintainer action — can't - be flipped from CI) -- [x] Workflow step gains a `::notice::` line so future runs surface - the deploy timestamp + sha without opening the verbose log -- [x] Verification recipe (`curl /api/version | jq .data.version`) + host-side fallback documented inline - -## Wave 5 — Hygiene (H1-H7) - -- [x] H1 sentinel parser malformed-enum hardening - (`SentinelParseResult.malformedEntries[]` + per-line typed - reasons; chat route splits `coach.keyvalues.parse_partial` from - the full-block `coach.keyvalues.parse_failed`) -- [x] H2 analytics-route unbounded `findMany` pagination - (cursor-paged 5 000-row chunks via `fetchBpSeriesChunked`, - `analytics.bp_in_target.row_count` wide-event meta for slow- - query attribution; integration test seeds 6 000 rows across a - chunk boundary) -- [x] H3 `` controlled-prop refactor - (`useResettableValue` hook + pure `nextResettableValue` helper; - `key={prefill}` removed from the parent mount) -- [x] H4 per-user prompt-tuning surface (Coach settings cog return) - (`User.coachPrefsJson` migration `0038_coach_prefs`; new - `GET/PUT /api/auth/me/coach-prefs`; settings cog opens a - right-edge ``; system-prompt prepends a per-user - OVERRIDE; snapshot reads prefs BEFORE measurement queries so - `excludeMetrics` filters before the snapshot lands) -- [x] H5 schema drift on `medication_schedules.days_of_week` - (decision: deploy — column is referenced by 9 source files - across 4 user-visible surfaces; migration - `0039_medication_schedule_days_of_week`, NULL = daily) -- [x] H6 Pearson p-value normal-approx replacement - (raised `MIN_PAIRED_N` from 14 → 20 — conservative patch; - rigorous incomplete-beta queued as a v1.4.24 candidate) -- [x] H7 Coach helpful/unhelpful first-week observation view - (polymorphic `RecommendationFeedback.target_type` migration - `0040_recommendation_feedback_target_type`; new POST - `/api/insights/chat/messages/:id/feedback`; aggregator slice - `buildCoachFeedbackBuckets` by (promptVersion, tone, verbosity); - admin section `/admin/coach-feedback` with the H7 i18n bundle - both EN + DE) - -W5 commits (8): 58ae9bc, fa07748, 9413d29, 3f60c81, 0eda1de, 1faee95, -05c7f14, plus this STATE tick. - -Test deltas: 2191 → 2223 unit (+32). Integration suite added 3 -files: `coach-prefs`, `coach-feedback`, plus the 6000-row chunk -boundary regression in the existing `bp-in-target` test. - -OpenAPI registry coverage: +3 routes (GET / PUT -`/api/auth/me/coach-prefs`, POST -`/api/insights/chat/messages/{id}/feedback`). - -## Wave 6 — Multi-agent QA + Product-Lead review - -- [x] code-reviewer -- [x] security review -- [x] design / UX review -- [x] senior-dev review -- [x] simplify -- [x] Product Lead — v1.5 P1 (iOS first launch) plan refresh with - concrete file paths now that backend contracts exist -- [x] Reconcile — 1 CRITICAL + all 9 HIGH + 4 of 5 simplify + 3 MED - applied; remainder triaged into `.planning/v1423-backlog.md` - -W6 reconcile commits (11): 5486507, 13977bc, d5202e5, a2dfc5e (Session A -— CRIT + HIGH 1-3); 3ee6dab, dfffe6b, d25e50e (Session B — HIGH 4 + -S-02-S-04 simplify + HIGH 6); deadc73 (Session D — HIGH 5); 650f150 -(Session E — HIGH 7); 1a46bfe (Session F — MED cluster); plus this -STATE tick. - -Detailed reconcile report: `.planning/phase-W6-v1423-reconcile-report.md`. -Deferred items: `.planning/v1423-backlog.md` (settings-cog vs -per-message-controls debate, rigorous Pearson incomplete-beta, surplus -MEDs, all LOWs, S-05 simplify, pre-existing `coach-prefs.test.ts` mock -issue, sandbox `git commit` no-op). - -Test deltas: W5 ended at 2223 unit. Session A added the `revoke.test.ts` - -- `apns-dispatch` device tests landing at 2227. Session E ended at 2235. - Session F added the forged-X-Device-Id regression — final at 2236 unit - (+45 across the W5+W6 reconcile arc). - -## Wave 7 — Release v1.4.23 - -- [x] Pre-release verify (typecheck / lint / openapi:check / 2236 - unit / 110 of 112 integration — 2 failures are the pre-existing - `coach-prefs.test.ts` `NextRequest` URL mock carryover from - v1.4.22, documented in `v1423-backlog.md`, non-blocking) -- [x] Format sweep commit (`f1e6630` prettier sweep on `develop`) + - W1 research + W5/W6 review packs commit (`766a9ae`) + - OpenAPI regen (`4183552`) -- [x] Bump `package.json` 1.4.22 → 1.4.23 + CHANGELOG (`d2331d9` - `chore(release): v1.4.23`) -- [x] Release-merge develop → main (`0dc0e16 Release v1.4.23`) -- [x] Tag + push v1.4.23 -- [x] GHCR build green -- [x] Coolify deploy + `/api/version=1.4.23` confirmed -- [x] Production smoke (gated routes 307; `/api/version=1.4.23`) -- [x] GH release published -- [x] Docs site + landing site sync (image pins 1.4.22 → 1.4.23 + - ai-insights v1.4.23 callout; softwareVersion JSON-LD bump) -- [x] `docs/audit/v1423-summary.md` release brief -- [x] v1.5 P1 plan refresh recorded - (`.planning/phase-W6-v1423-product-lead-review.md` section C) +Derived / synthesized wellness metrics, honest on the daily-snapshot data HealthLog +actually stores; full provenance + standards-linking; full docs audit; +discoverability / growth. See `ROADMAP.md`. + + diff --git a/CHANGELOG.md b/CHANGELOG.md index d72c050b9..9208e7ba6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## [1.9.1] — 2026-06-02 — No assessment warm on a page visit + +### Fixed + +- **Opening the Insights overview no longer regenerates every assessment.** A returning visit fired a one-per-session background warm that asked the provider to regenerate the comprehensive briefing and every category and metric assessment at once — a burst that could run for minutes and make the whole app feel unresponsive while it ran, recovering on its own once it finished. Assessments are kept fresh by the nightly pass, and each metric's text refreshes gently on its own when you open it; the "prepare assessments" button still regenerates everything on demand. A page visit now only reads the cached text. + ## [1.9.0] — 2026-06-02 — Insights time-ranges, deeper mood, medication coding, stability ### Added diff --git a/docs/api/openapi.yaml b/docs/api/openapi.yaml index 7db80f1fe..74d4f5919 100644 --- a/docs/api/openapi.yaml +++ b/docs/api/openapi.yaml @@ -1,7 +1,7 @@ openapi: 3.1.0 info: title: HealthLog API - version: 1.9.0 + version: 1.9.1 description: >- Self-hosted personal-health-tracking PWA — public API surface for the iOS native client and external ingest. diff --git a/package.json b/package.json index 3f99bd8ed..035b30e57 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "healthlog", - "version": "1.9.0", + "version": "1.9.1", "description": "Self-hosted personal-health-tracking PWA with Withings integration, AI insights, and doctor-report PDF export.", "license": "AGPL-3.0-only", "homepage": "https://healthlog.dev", diff --git a/src/app/insights/page.tsx b/src/app/insights/page.tsx index e3fd38cf6..d4912eb09 100644 --- a/src/app/insights/page.tsx +++ b/src/app/insights/page.tsx @@ -1,6 +1,5 @@ "use client"; -import { useEffect } from "react"; import { useQuery } from "@tanstack/react-query"; import dynamic from "next/dynamic"; import Link from "next/link"; @@ -152,16 +151,12 @@ export default function InsightsPage() { const analyticsQuery = useAnalyticsQuery(); const analytics = analyticsQuery.data as AnalyticsData | undefined; - // v1.8.7.1 — on-demand full assessment warm. The auto-trigger fires once - // per session as soon as the overview confirms the user has data, so the - // status cards (and the iOS client, which reads the same routes) land on - // warm caches without the user waiting. The manual button below re-warms - // everything on demand. - const { warm, isWarming, autoWarmOnce } = useInsightsWarm(); - const hasData = !!data && data.totalMeasurements > 0; - useEffect(() => { - autoWarmOnce(isAuthenticated && hasData); - }, [autoWarmOnce, isAuthenticated, hasData]); + // The "prepare assessments" button below re-warms every assessment on + // demand. There is no warm-on-mount: the nightly cron (04:30) keeps the + // caches warm and the per-metric status GETs revalidate gently on their + // own (stale-while-revalidate), so opening the overview only reads cached + // text — it never fans out a full provider warm on a page visit. + const { warm, isWarming } = useInsightsWarm(); // Empty-state shortcut — only paint once the comprehensive query has // resolved AND reported zero measurements. While it's in-flight we diff --git a/src/components/insights/use-insights-warm.ts b/src/components/insights/use-insights-warm.ts index 53934b35f..5120d5052 100644 --- a/src/components/insights/use-insights-warm.ts +++ b/src/components/insights/use-insights-warm.ts @@ -1,6 +1,6 @@ "use client"; -import { useCallback, useRef } from "react"; +import { useCallback } from "react"; import { useMutation } from "@tanstack/react-query"; import { toast } from "sonner"; @@ -8,7 +8,7 @@ import { queryKeys } from "@/lib/query-keys"; import { useTranslations } from "@/lib/i18n/context"; /** - * v1.8.7.1 — on-demand full assessment warm. + * On-demand full assessment warm. * * POSTs `/api/insights/pregenerate`, which enqueues a forced warm of every * AI assessment (comprehensive insight + the seven specialised status cards @@ -16,18 +16,13 @@ import { useTranslations } from "@/lib/i18n/context"; * the worker and returns immediately. The cards then fill via their existing * stale-while-revalidate GETs — there is nothing to invalidate here. * - * Two entry points share the one mutation: - * - `warm()` — the manual "prepare assessments" button. Shows a toast. - * - `autoWarmOnce(enabled)` — a fire-and-forget background warm fired ONCE - * per browser session (sessionStorage-gated) when the Insights overview - * mounts with data, so a returning user lands on warm caches without - * tapping anything. The auto path is silent (no toast) and tolerates a - * blocked/failed request — the server-side anti-spam bucket and the - * nightly cron are the catch-net. + * This is the explicit "prepare assessments" button only. There is no + * warm-on-mount: the nightly cron (04:30 Europe/Berlin) keeps every user's + * caches warm, and the per-metric status GETs revalidate gently on their + * own — so a page visit reads cached text instead of fanning out a full + * provider warm that would contend with foreground requests. */ -const AUTO_WARM_SESSION_KEY = "healthlog:insights:auto-warm"; - async function postWarm(): Promise { // Same-origin relative-path fetch — exempt from the safe-fetch rule by // construction. A 429 (warm already in progress) is an expected, @@ -47,12 +42,6 @@ export interface UseInsightsWarmResult { warm: () => void; /** True while the warm POST is in flight. */ isWarming: boolean; - /** - * Fire the background warm once per session. Pass `enabled` (e.g. - * authenticated AND the user has data) — the warm only fires on the - * first call where `enabled` is true, and never again this session. - */ - autoWarmOnce: (enabled: boolean) => void; } export function useInsightsWarm(): UseInsightsWarmResult { @@ -71,31 +60,8 @@ export function useInsightsWarm(): UseInsightsWarmResult { }); }, [mutate, t]); - // The auto-trigger must fire at most once per session even across the - // remounts a route navigation causes. A ref guards within the mount; - // sessionStorage guards across mounts / navigations. - const firedThisMount = useRef(false); - const autoWarmOnce = useCallback( - (enabled: boolean) => { - if (!enabled || firedThisMount.current) return; - firedThisMount.current = true; - try { - if (window.sessionStorage.getItem(AUTO_WARM_SESSION_KEY)) return; - window.sessionStorage.setItem(AUTO_WARM_SESSION_KEY, String(Date.now())); - } catch { - // sessionStorage can throw under strict-privacy modes — fall - // through and fire the warm anyway; the server bucket de-dupes. - } - // Silent background warm — no toast, errors swallowed by the - // mutation's default (no onError handler surfaces it). - mutate(); - }, - [mutate], - ); - return { warm, isWarming: mutation.isPending, - autoWarmOnce, }; }