Files
HealthLog/CHANGELOG.md
T
Marc-André Bombeck 79764d3467 fix(compose): replace ${VAR:?required} with entrypoint validation (1.3.1)
The ${VAR:?error} syntax in docker-compose.yml broke Coolify-style
deploys: that platform parses the compose file eagerly and stores the
fallback error string as the env-var value when the var is unset, which
then propagates into DATABASE_URL and breaks the running app with
P1000 auth failures.

Switched to plain ${VAR} interpolation. Validation moves into
docker-entrypoint.sh, which checks DATABASE_URL, SESSION_SECRET,
ENCRYPTION_KEY, API_TOKEN_HMAC_KEY before connecting to the database
and exits with a deploy-platform-agnostic error listing the unset vars.
2026-04-27 20:19:30 +02:00

14 KiB
Raw Permalink Blame History

Changelog

[1.3.1] — 2026-04-27

Fixed

  • Compose env-var validation no longer breaks Coolify-style deploys. docker-compose.yml previously used ${VAR:?required} shell-parameter syntax for the four secrets and POSTGRES_PASSWORD. Some hosting platforms (Coolify in particular) parse compose files eagerly and store the fallback error string ("POSTGRES_PASSWORD is required") as the literal env-var value when POSTGRES_PASSWORD was unset, which then collided with DATABASE_URL and broke the running app with P1000: Authentication failed. Compose now uses plain ${VAR} interpolation; validation moved into docker-entrypoint.sh, which fails fast with a clear stderr message listing the unset variables.

Notes

If you upgraded an existing Compose stack from 1.2.x → 1.3.0 and hit the POSTGRES_PASSWORD is required literal-as-value bug, set POSTGRES_PASSWORD in your environment to whatever your existing Postgres data volume was originally initialised with (likely healthlog if you started from a pre-1.2.1 release), then redeploy. Postgres only honours POSTGRES_PASSWORD on first volume init — the existing user keeps the original password regardless of env changes.

[1.3.0] — 2026-04-27

Added — Body composition + targeted hardening

  • Total body water and bone mass as measurement types (closes #89). New enum values TOTAL_BODY_WATER and BONE_MASS, both stored canonically in kilograms (matches Withings hydration/bone-mass measures and Health Connect's TotalBodyWaterRecord / BoneMassRecord). Migration is purely additive (ALTER TYPE ... ADD VALUE) — safe to apply against any 1.2.x database without downtime.
  • Withings sync picks both up automatically. The Withings client now maps measure type 77 (hydration / water mass) and 88 (bone mass). Anyone with a Withings Body+ scale and an active connection will see the new metrics flowing in on the next sync without any extra config.
  • Doctor-report PDF includes both new types in the vital-signs table when data exists, with locale-aware labels in English and German.
  • Dashboard widgets registered for both (default-invisible — opt in via Settings → Dashboard layout).

Security

  • SSRF guard hardened (isPublicUrl). The previous implementation used parseInt with permissive prefix checks like h.startsWith("10.") which let 010.0.0.1 slip through — and worse, the WHATWG URL parser silently normalises 010.0.0.1 to 8.0.0.1 (octal interpretation), a real bypass on naive checks. The new guard adds a pre-URL leading-zero check on the raw input, a strict IPv4 parser, and proper IPv6 bracket / loopback / link-local handling. Now blocks 127.0.0.0/8, 0.0.0.0/8, 10.0.0.0/8, 169.254.0.0/16, 172.16.0.0/12, 192.168.0.0/16, 100.64.0.0/10 (CGNAT), ::1, fe80::/10, fc00::/7. Comprehensive regression tests included.
  • GitHub PAT redacted from logged error bodies. When a feedback escalation to GitHub fails, the response body was being passed verbatim into getEvent()?.addWarning(...) — flowing to Loki. The body is now stripped of the configured token before logging.
  • Per-user threshold writes are rate-limited (30 writes / 5 min) to make audit-log enumeration unattractive. Audit-logging itself was already there.

Performance

  • N+1 in /api/insights/comprehensive fixed. The medication-compliance loop hit Postgres once per active medication. It now batches into a single medicationId IN (...) query and groups in memory. Latency improvement scales linearly with the user's active medication count.

Reliability

  • pg-boss draining on SIGTERM/SIGINT. docker stop, Coolify redeploys, and Kubernetes pod terminations now trigger boss.stop({ graceful: true, timeout: 30s }) so in-flight handlers finish instead of being killed. Previously, pending handlers could be lost or replayed on restart.
  • CI now blocks on TypeScript strict checks. continue-on-error: true removed for typecheck. Tests already were blocking. ESLint stays non-blocking for now — the existing settings/page.tsx monolith has long-standing react-hooks/set-state-in-effect violations whose proper fix lives in the 1.4.0 settings-split refactor; a blocking lint here would just gate every PR until that refactor lands. Required cleaning up two any types in api-handler.ts (justified inline — Next.js variadic handler signature constrained by Promise<Response> return).

Polish

  • Bottom-nav touch targets sized to WCAG 2.5.5 minimum (44×44 CSS px). Visual icon stays at 20 px so the design doesn't shift.
  • Phase-config dialog marks the decorative coloured dot aria-hidden because the redundant text label already conveys the phase to screen readers. No more meaningless image node announcements.
  • Admin-status labels for "Web Push" and "Bug Report" now go through t() (new admin.integrationWebPush / admin.integrationBugReport keys in en + de). They were the last hard-coded English strings on the admin page.

What's not in this release (tracked for later)

  • Onboarding redesign (dashboard-first empty-state flow + persistent Getting Started checklist) and the typed apiClient wrapper that underpins it are tracked for a focused 1.4.0 cycle. The 1.2.1 patch already closed the acute symptoms of #87 (silent-failure toast + default schedule), so the redesign is now a proper UI investment rather than a bug-fix.
  • Withings sync is mapping both new measures, but a dedicated Bearer-auth ingest endpoint for external pipelines (n8n + Health Connect, requested in #89) ships in 1.4.0 alongside the API-token flow.

[1.2.1] — 2026-04-27

Fixed

  • Onboarding: Medications added during onboarding are now actually persisted (closes #87). The wizard previously sent an empty schedules: [] array, the server-side validation rejected it with a 422, the client never checked response.ok, and the user was redirected to the dashboard as if everything had worked. Onboarding now wraps each step in try/catch, surfaces failures via toast, and attaches a default reminder window (08:00–09:00 daily) so the medication actually persists. A hint under the medication list explains the default.
  • Docker setup (closes #88):
    • docker-compose.yml now uses ports: "3000:3000" (was expose: "3000", which made the app unreachable from the host).
    • POSTGRES_PASSWORD is a single env var that both the Postgres service and DATABASE_URL interpolate, so they cannot drift apart.
    • .env.example now points at the in-container hostname db:5432 (was localhost:5432, which never resolves inside the app container).
  • Documentation:
    • package.json synced to 1.2.0 (was lagging on 1.1.0).
    • CLAUDE.md and AGENTS.md corrected to 23 models (the Feedback model added in v1.2 was missing from the count).
    • README.md Quick Start gives a realistic time estimate, generates the four secrets in one block straight into .env, and points reverse-proxy users at the docs.

Added — Tooling & Supply Chain

  • Pre-built multi-arch images on GHCR: .github/workflows/docker-publish.yml now builds linux/amd64 + linux/arm64 images on every push to main and on every v* tag, publishing to ghcr.io/mbombeck/healthlog. Self-hosters no longer need a build toolchain — docker compose pull && docker compose up -d is enough. The bundled docker-compose.yml references the published image with a build: block as fallback for contributors.
  • Supply-chain attestations: each published image carries a SLSA build provenance statement and a Software Bill of Materials. SECURITY.md documents how to verify them and how to pin a specific version.
  • Documentation single source of truth: getting-started/installation.mdx is now the canonical setup guide (mirrors the bundled docker-compose.yml); self-hosting/docker.mdx slimmed to image internals + ops notes only. The landing page's Quick Start terminal block now includes the secrets-generation step (was missing).

Notes

This is a patch release that closes the install/onboarding friction reported in #87 and #88. The bigger user-facing changes (additional measurement types like total body water and bone mass per #89, full onboarding redesign, typed API client) are tracked for 1.3.0.

[1.2.0] — 2026-04-18

Added — Personalization, Glucose & Multi-Provider AI

  • Per-user custom thresholds: Override the computed default ranges (BP, BMI, glucose, pulse) with values from your clinician. Audit-logged with previous/new values and timestamps. Doctor Report PDF flags custom ranges and prints both your target and the standard guideline value.
  • Blood glucose tracking: New metric with fasting, postprandial, random, and bedtime contexts. Display unit switch between mg/dL and mmol/L (lossless conversion). Context-aware classification per ADA 2024 / DGIM. Per-context charting on dashboard and Doctor Report PDF.
  • Dashboard customization: Show/hide and drag-to-reorder every dashboard widget. Per-user preference, reset-to-defaults button. Layout persists across the same user on the same device.
  • Built-in feedback system: New in-app Send Feedback flow (Bug / Feature / Question / Other) with anonymized system info attachment. Stored in HealthLog's own database — no GitHub config required. Optional Escalate to GitHub button for admins who configure a PAT.
  • Multi-provider AI insights: Provider abstraction extended with Anthropic Claude and local OpenAI-compatible endpoints (Ollama, LM Studio, vLLM, LiteLLM) alongside OpenAI. Per-user provider selection. Local endpoints keep all health data on your network.
  • Locale-aware UI polish (English-first): Numbers, dates, glucose units, BP, weight, and BMI all formatted via useFormatters() from the active locale. Doctor Report PDF and AI insight prompts now respect locale end-to-end (no hand-rolled Intl.* with fixed locales).

Changed

  • Reference range computation extracted into a dedicated src/lib/health/thresholds.ts module with computed defaults and override resolution.
  • AI provider routing reworked to dispatch by provider field on the user record; OpenAI remains the default for legacy users.
  • Dashboard route renders widgets from UserDashboardLayout model when present, otherwise falls back to the default order.
  • Doctor Report PDF: locale-aware headers, glucose section, custom-range badges.

Security

  • GitHub PAT for feedback escalation stored AES-256-GCM encrypted in the database (never as env var).
  • Local AI endpoint URLs validated against SSRF (no localhost/RFC1918 unless explicitly allowed by admin).
  • Custom threshold writes rate-limited and audit-logged with IP.

[1.1.0] — 2026-04-06

Added — AI Insights Overhaul

  • ChatGPT Proxy Integration: Insights now run through a local openai-oauth proxy using your ChatGPT subscription — no separate API billing required
  • Admin AI Fallback: Admins can configure a global API key (OpenAI/OpenRouter) as fallback for users without their own connection
  • Provider Abstraction: New src/lib/ai/ module with pluggable providers (Codex OAuth, Admin Key, None) and automatic failover
  • Medical Insight Prompts: 7 specialized prompts based on ESC/ESH 2023, WHO, DGE, and DEGAM guidelines
    • Blood Pressure: ESC/ESH classification, morning risk ladder (J-HOP), pulse pressure, seasonal variation
    • Weight: 5%/10% milestone recognition, plateau detection, body composition divergence
    • Pulse: Fitness interpretation ladder, 80-100 bpm elevated-risk band, rate-pressure product
    • BMI: Age-adjusted DEGAM classification for 65+
    • Medication Compliance: Chronotherapy hints, mood-adherence risk prediction, 90-day tracking
    • General Status: Cross-domain synthesis with cardiovascular risk stratification
    • Mood: Bidirectional correlations with vitals and adherence
  • Enriched Feature Extraction:
    • Sleep duration and activity steps (previously ignored)
    • Rate-Pressure Product (pulse × systolic BP, myocardial demand indicator)
    • Body composition divergence flag (weight stable + body fat rising)
    • Mood-adherence risk predictor
    • Seasonal BP variation (winter vs summer, requires >180 days data)
    • BP standard deviation (sdSys30/sdDia30) as variability risk marker
    • Pulse pressure (arterial stiffness marker)
    • 5 cross-metric Pearson correlations (weight↔BP, pulse↔BP, mood↔pulse, mood↔BP, mood↔weight)
    • 90-day averages and all-time statistics for all metrics
    • Historical comparison (current 7d vs previous 30d baseline)
  • New UI Components:
    • InsightStatusCard: Compact per-metric status card with classification indicator and fade-in animation
    • InsightAdvisorCard: Premium structured card with findings, correlations, recommendations (ready for integration)
  • OAuth Routes: /api/auth/codex/authorize, /callback, /disconnect for ChatGPT connection
  • Admin AI Settings: /api/admin/ai-settings for global API key management

Changed

  • Insight prompts now use personal advisor tone ("dein Blutdruck") with positive-first pattern
  • Reasoning scaffold in system prompt (What changed? → Why? → What to do?)
  • Conditional correlation instructions (only mention when |r| > 0.4)
  • InsightResult schema enriched with insightType, primaryRecommendation, classificationLabel
  • BP target calculation now uses paired readings (both sys AND dia must be in range simultaneously)
  • Medication streak tracking extended from 7-day to 30-day window
  • CSP updated to allow chatgpt.com for OAuth flow

Security

  • Rate limiting on all OAuth and admin endpoints
  • PKCE (S256) + state parameter for OAuth CSRF protection
  • Encrypted token storage at rest (AES-256-GCM)
  • Error messages truncated to prevent upstream response body leaks
  • Admin key preview shows last 4 chars of decrypted key (not ciphertext prefix)
  • prefers-reduced-motion support for insight card animations

Removed

  • openaiKeyEncrypted field from User model (replaced by provider abstraction)
  • Direct OpenAI API calls from insight generators (now routed through provider)
  • Legacy API key input in settings UI (replaced by ChatGPT connect button)

[1.0.1] — Previous release