Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
102 commits
Select commit Hold shift + click to select a range
3594b54
feat(sleep): place sleep-debt and rhythm cards side by side when both…
MBombeck Jun 14, 2026
2d1c02c
merge(sleep): side-by-side sleep-debt and rhythm cards when both present
MBombeck Jun 14, 2026
8b5b35b
docs(self-hosting): document the notification channels and Web Push s…
MBombeck Jun 14, 2026
82be867
merge(docs): self-hosting notification channels and Web Push guide
MBombeck Jun 14, 2026
355fa9a
feat(schema): add per-user Polar/Oura credentials, measurement remind…
MBombeck Jun 14, 2026
111fa7a
merge(schema): per-user Polar/Oura credentials, measurement reminders…
MBombeck Jun 14, 2026
bc12633
fix(sleep): reconstruct an ordered WHOOP hypnogram timeline
MBombeck Jun 14, 2026
bd467d0
fix(sleep): emit Fitbit sleep as per-segment rows, not a stage collapse
MBombeck Jun 14, 2026
7ad0b9c
fix(sleep): stamp Withings sleep segments at the END, not the START
MBombeck Jun 14, 2026
ddfed17
feat(sleep): backfill existing WHOOP + Withings sleep rows after the …
MBombeck Jun 14, 2026
38c7604
feat(integrations): per-user Polar and Oura OAuth credentials
MBombeck Jun 14, 2026
ed50d10
merge(sleep): reconstruct WHOOP per-stage timeline, fix Withings/Fitb…
MBombeck Jun 14, 2026
80f3cd8
chore(migrations): renumber sleep-timeline backfill to 0163 for monot…
MBombeck Jun 14, 2026
9ef3510
merge(integrations): per-user Polar and Oura OAuth credentials
MBombeck Jun 14, 2026
5b756f0
feat(onboarding): add User.onboardingGoals for goal persistence
MBombeck Jun 14, 2026
5f60142
feat(onboarding): persist goal selection and seed the dashboard
MBombeck Jun 14, 2026
c8cd5aa
feat(onboarding): add an optional anamnesis step on baseline
MBombeck Jun 14, 2026
1e28f9c
merge(onboarding): persist goals to seed the dashboard, add anamnesis…
MBombeck Jun 14, 2026
1c33161
chore(migrations): renumber onboarding-goals to 0164 for monotonic order
MBombeck Jun 14, 2026
82e2d7d
fix(import): bound JSON measurement timestamps and dedup on externalId
MBombeck Jun 14, 2026
fbdcb31
feat(import): add CSV measurement import with per-row status and dry-run
MBombeck Jun 14, 2026
f0a14ac
feat(measurements): surface that the entry timestamp is editable for …
MBombeck Jun 14, 2026
b3d2753
merge(import): CSV measurement import, backdate hint, JSON-import ded…
MBombeck Jun 14, 2026
988ba11
feat(reminders): server-authoritative next-due engine for preventive-…
MBombeck Jun 14, 2026
0a4f00f
feat(reminders): CRUD + manual satisfy routes for preventive-care rem…
MBombeck Jun 14, 2026
383963d
feat(reminders): every-15-min dispatcher for preventive-care reminders
MBombeck Jun 14, 2026
8058d81
feat(notifications): MEASUREMENT_REMINDER event type and client-manag…
MBombeck Jun 14, 2026
c604548
feat(reminders): preventive-care surface — wann, was, wo
MBombeck Jun 14, 2026
946a9f7
docs(api): document the preventive-care reminder routes
MBombeck Jun 14, 2026
4dcaee5
merge(reminders): measurement/Vorsorge reminders with auto-resolve an…
MBombeck Jun 14, 2026
271cc0d
feat(labs): add structured lab-result store with /api/labs CRUD
MBombeck Jun 14, 2026
fbecb6b
feat(labs): surface lab results in the doctor report and FHIR export
MBombeck Jun 14, 2026
72fb51c
feat(labs): add the Laborwerte page, navigation, and OpenAPI contract
MBombeck Jun 14, 2026
4f03962
merge(labs): structured lab results with reference ranges, doctor-rep…
MBombeck Jun 14, 2026
44eed87
fix(vorsorge): pair the loading spinner with motion-reduce:animate-none
MBombeck Jun 14, 2026
fd5cc78
refactor(mood): read the dashboard and analytics mood series from one…
MBombeck Jun 14, 2026
b1a81bb
refactor(insights): unify the learning-state gates behind one primitive
MBombeck Jun 14, 2026
a5ec4ef
feat(notifications): add silent medication-intake cross-device sync push
MBombeck Jun 14, 2026
836fd74
feat(medications): dispatch the cross-device intake sync on a dose mu…
MBombeck Jun 14, 2026
6d8dd75
feat(devices): accept a Live Activity push token on registration
MBombeck Jun 14, 2026
3606610
merge(medications): silent intake-sync push and Live Activity token f…
MBombeck Jun 14, 2026
c90459a
refactor(nav): drive desktop and mobile from one destination model
MBombeck Jun 14, 2026
9bedd6b
feat(nav): give the Coach a single primary nav home
MBombeck Jun 14, 2026
fd39580
feat(measurements): add ANS_CHARGE and CARDIO_LOAD measurement types
MBombeck Jun 14, 2026
574b860
feat(polar): complete the AccessLink pull surface
MBombeck Jun 14, 2026
87e570a
fix(polar): lift invalid_grant to reauth and harden the credentials s…
MBombeck Jun 14, 2026
3cdbab9
feat(measurements): add Sleep Score and body-temperature deviation types
MBombeck Jun 14, 2026
c5e91c4
feat(oura): pull the real sleep timeline, SpO2, Sleep Score and more
MBombeck Jun 14, 2026
a0662d1
fix(oura): audit credential deletes and trim integration vestiges
MBombeck Jun 14, 2026
412b22e
merge(polar): pull ANS charge, cardio load, SpO2, distance; fix sleep…
MBombeck Jun 14, 2026
2736e23
merge(oura): real sleep timeline, SpO2, sleep score, temperature devi…
MBombeck Jun 14, 2026
cf31211
refactor(settings): gather layout personalization under one home
MBombeck Jun 14, 2026
347143c
fix(personal-records): map the new derived metric types to a null PR …
MBombeck Jun 14, 2026
d1055b4
merge(coherence): unify nav, layout settings, mood source, learning g…
MBombeck Jun 14, 2026
794192c
feat(insights): add a calm device-score tile for vendor-native metrics
MBombeck Jun 14, 2026
e063f79
feat(sleep): surface the nightly sleep-quality scores on the Sleep page
MBombeck Jun 14, 2026
866f3dc
feat(insights): add a Recovery sub-page for the device-native strain …
MBombeck Jun 14, 2026
0be1a6b
feat(insights): surface the Oura body-temperature deviation on skin t…
MBombeck Jun 14, 2026
1b0c4b2
test(insights): pin the device-score data-gating across the new surfaces
MBombeck Jun 14, 2026
168b69b
merge(insights): surface stored sleep/recovery/strain metrics and a r…
MBombeck Jun 14, 2026
aec41e1
feat(notifications): register webhook + email channel types
MBombeck Jun 14, 2026
7532bbc
feat(notifications): add generic webhook channel
MBombeck Jun 14, 2026
909c2aa
feat(notifications): add SMTP email channel
MBombeck Jun 14, 2026
17dd10c
feat(admin): operator-wide notification health panel
MBombeck Jun 14, 2026
c04bdc8
feat(notifications): document SMTP env and translate channel strings
MBombeck Jun 14, 2026
45ccdcb
merge(notifications): generic webhook + SMTP email channels and opera…
MBombeck Jun 14, 2026
d0ba4b0
feat(notifications): generate VAPID keys from the admin panel
MBombeck Jun 14, 2026
266109f
docs(self-hosting): VAPID generate button, env names, backup and whit…
MBombeck Jun 14, 2026
a8ff52e
merge(admin): generate VAPID keys in-app and self-hoster backup/env docs
MBombeck Jun 14, 2026
001c158
feat(integrations): add a Setup-guide doc-link to every integration card
MBombeck Jun 14, 2026
78c110a
feat(settings): add a Reminders & Notifications home
MBombeck Jun 14, 2026
ac69107
chore(compose): whitelist VAPID and APNs env vars so the documented e…
MBombeck Jun 14, 2026
a27247f
merge(settings): reminders hub, uniform integration cards, per-integr…
MBombeck Jun 14, 2026
3d6127a
chore(labs): drop the unwired Vorsorge lab-lookup and its dead trend key
MBombeck Jun 14, 2026
a4b508f
merge(labs): drop orphaned lab read helper and dead trend query-key
MBombeck Jun 14, 2026
290f46f
fix(sleep): label Polar nights reconstructed and emit their awake time
MBombeck Jun 14, 2026
7a96a93
refactor(import): derive the CSV example header from the shared colum…
MBombeck Jun 14, 2026
5a62d04
fix(import): use the success token for import success ticks
MBombeck Jun 14, 2026
c6beba2
fix(import): keep the import cards on the SPA and balance the trio
MBombeck Jun 14, 2026
9f960a5
feat(import): show character counts and a Preview busy state
MBombeck Jun 14, 2026
dad0b89
fix(insights): unify device-score tiles onto the shared card and skel…
MBombeck Jun 14, 2026
bc237d6
fix(labs): converge the lab tile + loading onto the shared primitives
MBombeck Jun 14, 2026
dba107d
fix(vorsorge): add a loading state and align the cards on the shared …
MBombeck Jun 14, 2026
7637657
merge(insights): unify device-score/labs/vorsorge loading, empty, and…
MBombeck Jun 14, 2026
6a994cd
feat(nav): give Vorsorge a nav home and fold the utility tail into on…
MBombeck Jun 14, 2026
3fc195e
feat(settings): add a back-to-hub link on the Layout child editors
MBombeck Jun 14, 2026
34875ce
merge(import): success token, CSV header single-source, Link nav, res…
MBombeck Jun 14, 2026
eab86b2
merge(nav): top-level Vorsorge home, shared utility destinations with…
MBombeck Jun 14, 2026
b87ded0
fix(notifications): bound the SMTP transport with explicit timeouts
MBombeck Jun 14, 2026
33e7f20
perf(reminders): bound the Vorsorge cron scan to due reminders
MBombeck Jun 14, 2026
790fecd
fix(doctor-report): aggregate sleep per night, not per stage
MBombeck Jun 14, 2026
b336ed5
fix(settings): confirm VAPID regenerate in-app; let labs subtitle wrap
MBombeck Jun 14, 2026
aeb3046
chore(observability): add headerValue to the redact denylist
MBombeck Jun 14, 2026
1ed2e5d
docs(recovery): note the date-anchor bucketing gap for negative-UTC u…
MBombeck Jun 14, 2026
7574ea0
merge(reliability): SMTP timeouts, cron index use, doctor-report slee…
MBombeck Jun 14, 2026
38a2c71
fix(integrations): audit WHOOP and Fitbit credential teardown
MBombeck Jun 14, 2026
9430e89
feat(integrations): unify OAuth returns and the status envelope
MBombeck Jun 14, 2026
d7691b6
merge(integrations): honest Polar sleep timeline, shared reconstructi…
MBombeck Jun 14, 2026
497982b
chore(release): v1.17.1 — preventive care, lab results, and more of t…
MBombeck Jun 14, 2026
78f0e06
fix(migrations): reference the mapped measurement_type enum in the re…
MBombeck Jun 14, 2026
cbd5c2b
fix(withings): key sleep re-sync on the stable externalId, not the sh…
MBombeck Jun 15, 2026
f903b32
test(rollups): compare rounded rollup means to live SQL within hundre…
MBombeck Jun 15, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 41 additions & 1 deletion .env.production.example
Original file line number Diff line number Diff line change
Expand Up @@ -74,16 +74,56 @@ API_TOKEN_HMAC_KEY="<openssl rand -hex 32>"
# OURA_REDIRECT_URI=""


# -----------------------------------------------------------------------------
# Web Push -- VAPID keys (optional; the admin panel is the easier path)
# -----------------------------------------------------------------------------
# Web Push needs NO Apple account and no paid service -- just a VAPID keypair.
# The EASIEST path is the admin panel: /admin -> Web Push VAPID -> "Generate
# keys" mints and stores the pair for you (private key encrypted at rest). Use
# these env vars only if you prefer env-config over the DB. The loader reads
# DB first, then env (src/lib/notifications/vapid-config.ts). NOTE: these are
# NOT in docker-compose.yml's `environment:` whitelist -- if you go the env
# route under compose, add them there too or the values never reach the
# container (see docs/self-hosting/getting-started.md Troubleshooting).
# VAPID_PUBLIC_KEY=""
# VAPID_PRIVATE_KEY=""
# VAPID_SUBJECT="mailto:you@example.com"


# -----------------------------------------------------------------------------
# APNs -- iOS push (optional, all-or-none)
# -----------------------------------------------------------------------------
# Only the native iOS app uses APNs, and it needs a paid Apple Developer
# Program membership. Web Push (above) needs NO Apple account -- it covers
# every browser and the installed PWA on iPhone. Leave this whole block unset
# unless you ship the native app. Key-source precedence when more than one is
# set: APNS_KEY_B64 > APNS_KEY > APNS_KEY_FILE.
# APNS_KEY_ID=""
# APNS_TEAM_ID=""
# APNS_BUNDLE_ID=""
# APNS_KEY="" # OR APNS_KEY_FILE -- either one satisfies the manifest
# APNS_KEY_B64="" # PEM body base64-encoded -- recommended (escapes newlines)
# APNS_KEY="" # OR APNS_KEY (raw PEM) OR APNS_KEY_FILE -- one satisfies the manifest
# APNS_KEY_FILE=""


# -----------------------------------------------------------------------------
# SMTP -- email notification channel (optional, all-or-none)
# -----------------------------------------------------------------------------
# Operator SMTP transport for the email channel. SMTP_HOST + SMTP_PORT +
# SMTP_FROM together enable it; SMTP_USER/SMTP_PASS are optional (omit for an
# unauthenticated relay). SMTP_SECURE=true uses implicit TLS (port 465);
# unset/false uses STARTTLS (port 587). Per-user recipient address is set in
# Settings -> Notifications, not here. Missing the core trio disables the
# channel silently. Listed in docker-compose.yml's environment whitelist so
# these reach the container.
# SMTP_HOST=""
# SMTP_PORT="587"
# SMTP_FROM="HealthLog <noreply@example.com>"
# SMTP_USER=""
# SMTP_PASS=""
# SMTP_SECURE="false"


# -----------------------------------------------------------------------------
# Deploy webhook -- Coolify -> HealthLog auto-deploy feedback
# -----------------------------------------------------------------------------
Expand Down
39 changes: 39 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,45 @@

## [Unreleased]

## [1.17.1] — 2026-06-15 — preventive care, lab results, and more of the data you already have

This release closes the loop from tracking to acting. Preventive-care reminders tell you when to measure or check what, structured lab results give your bloodwork a home, and a new recovery view surfaces signals that were already being collected but never shown. Sleep timelines now read in real clock times, marking a dose on one device clears its reminder on the others within seconds, and self-hosters gain a generic webhook channel, email, one-tap Web Push setup and a proper notifications guide. Every new number is computed once on the server and read the same way on the dashboard, the coach, the doctor-report and the companion app. No breaking changes.

### Added

- **Preventive-care reminders.** Set a reminder to measure your blood pressure on a cadence or to schedule an annual blood panel, with a clear next-due date and where to do it. A matching measurement marks it done on its own; the rest you tick off. Reminders reach you over every notification channel, and the cards stay calm — status is a quiet badge, never an alarming colour.
- **Structured lab results.** Record bloodwork and biomarkers with their reference range, see each one trend over time, and carry them into the doctor-report PDF and the FHIR export. An out-of-range value is shown plainly, not in red.
- **A recovery view, and sleep quality in depth.** A new recovery page gathers strain, training load and autonomic-charge readings, and the sleep page gains an efficiency, performance and sleep-score block — metrics a connected device was already sending that nothing surfaced before. Each appears only once it has data and stays calm until it has enough to be sure.
- **Import and backdated entry.** Import a CSV of past measurements with a previewed, per-row result and unit conversion, and log an entry with a past date and time — the cold-start escape hatches for bringing existing history in.
- **Onboarding that does what it says.** A short, skippable health-baseline step, and the goals you pick now actually seed your dashboard.
- **Polar and Oura credentials in the browser.** Both connect with your own developer-app credentials entered in settings, like the other integrations — no environment file needed.
- **More ways to be notified.** A generic webhook channel reaches a self-hosted notifier or a chat service, an email channel sends over your own SMTP server, and an operator can see delivery health across every channel. Web Push keys can be generated in one click from the admin panel.

### Changed

- **Sleep reads in real clock times.** Per-stage rows now carry their own start and end, so the "last night" timeline lays out across the night instead of stacking — measured where the device reports stage timing, and an honestly-labelled reconstruction where it only reports stage totals. Nights logged by more than one source resolve to a single total.
- **A dose taken on one device clears the others in seconds.** Marking a dose sends a silent sync to your other devices, so a lock-screen reminder ends without waiting for the next app open.
- **More of a connected device's data flows in.** Readiness contributors, body-temperature deviation, blood-oxygen, a sleep score and autonomic-charge and training-load now come through where the source provides them. Body weight is never taken from a wearable strap.
- **One product across every screen.** Desktop and mobile navigation now tell the same story, the coach has a single home, the layout and reminder settings each gather under one hub, and every integration card links to its setup guide.

### Fixed

- Sleep nights from some sources were stamped at the wrong instant and sat shifted earlier in the day; corrected, with a one-time backfill that re-syncs affected nights.
- The doctor-report sleep figure now matches the dashboard and the companion app, reading the same reconstructed per-night total as every other surface.
- Polish across the new surfaces: consistent loading and empty states, design-token colours, responsive grids, and a calm confirmation for regenerating Web Push keys.

### Security

- The webhook channel pins a public host and refuses a private or loopback address unless explicitly allowed; webhook secrets, SMTP credentials and the Web Push private key are kept out of any recorded error; the key-generation and delivery-health endpoints are reachable only from an authenticated admin session, never a token.

### Self-hosting

- A notifications guide spells out that no Apple account is needed — Web Push, Telegram, ntfy, the new webhook and email all work without one — alongside a backup-and-restore callout and a clearer note on which variables must be whitelisted to reach the container.

### Operator note

- Migrations 0162–0166 are additive. A boot-time backfill re-syncs sleep nights from the affected sources to the corrected timeline once; it is idempotent and bounded.

## [1.17.0] — 2026-06-14 — clinical depth for glucose and sleep, and three new sources

This release builds new depth on the coherent foundation laid over the v1.16 line. Blood glucose gains a clinical panel, sleep gains a debt and chronotype reading, and three new data sources — Nightscout, Polar and Oura — join Withings, WHOOP, Fitbit and Apple Health. Every new metric is computed once on the server and read the same way on the dashboard, the coach, the doctor-report and the companion app, and each holds back a confident reading behind a calm "still learning" state until it has enough data. No breaking changes.
Expand Down
29 changes: 29 additions & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,35 @@ services:
# container (the `environment:` block is a whitelist). See
# docs/ops/tls-cert-pin.md.
TLS_LEAF_SPKI_PINS: "${TLS_LEAF_SPKI_PINS:-}"
# v1.17.1 — SMTP transport for the email notification channel (optional,
# all-or-none: HOST + PORT + FROM enable it; USER/PASS optional; SECURE
# toggles implicit TLS). Listed here so operator values in .env reach the
# container (the `environment:` block is a whitelist — vars not listed
# never propagate from .env, even with `${VAR}` substitution at
# compose-up). Per-user recipient is stored in the DB, not here.
SMTP_HOST: "${SMTP_HOST:-}"
SMTP_PORT: "${SMTP_PORT:-}"
SMTP_FROM: "${SMTP_FROM:-}"
SMTP_USER: "${SMTP_USER:-}"
SMTP_PASS: "${SMTP_PASS:-}"
SMTP_SECURE: "${SMTP_SECURE:-}"
# v1.17.1 — Web Push VAPID keys (optional). The admin Settings panel stores
# these in the DB (the supported path); these env vars are the fallback the
# loader reads when the DB pair is unset. Listed here so the documented env
# path reaches the container under the bundled compose. See
# docs/self-hosting/notifications.md.
VAPID_PUBLIC_KEY: "${VAPID_PUBLIC_KEY:-}"
VAPID_PRIVATE_KEY: "${VAPID_PRIVATE_KEY:-}"
VAPID_SUBJECT: "${VAPID_SUBJECT:-}"
# APNs for a self-hosted native iOS build (optional, env-only — no admin UI
# for the .p8). APNS_KEY_B64 is the escape-free base64 form preferred over
# APNS_KEY/APNS_KEY_FILE; the key must be scoped "Sandbox & Production".
# PWA users need none of this (Web Push covers them). See
# docs/self-hosting/notifications.md.
APNS_KEY_B64: "${APNS_KEY_B64:-}"
APNS_KEY_ID: "${APNS_KEY_ID:-}"
APNS_TEAM_ID: "${APNS_TEAM_ID:-}"
APNS_BUNDLE_ID: "${APNS_BUNDLE_ID:-}"
depends_on:
db:
condition: service_healthy
Expand Down
Loading
Loading