Releases: MBombeck/HealthLog
Release list
v1.28.29 — Dashboard edits show up on the way back; one scrollbar again
Changing the dashboard tile selection now shows up immediately when you navigate back — the third and final layer of this bug. Saving marked the dashboard's cached data stale but, with the dashboard page unmounted, nothing re-read it until the next poll or a window-focus flick. The save now refreshes that cache directly, mounted or not.
The settings pages with sortable lists (dashboard layout, medication order, modules, mood tags) showed a second page scrollbar: an invisible screen-reader hint below each list escaped its container and silently lengthened the document. All five editors are fixed and the overscroll guard now covers these routes.
v1.28.28 — OpenAI-compatible gateways work; a re-keyed night can no longer vanish
The user-level "Local (OpenAI-compatible)" provider now speaks the standard JSON wire: it sends response_format and, when an endpoint rejects it, falls back once and remembers that endpoint's dialect — so LiteLLM, OpenRouter, vLLM, LM Studio and plain Ollama all work from the same settings form. The form and the provider docs now say so explicitly. Gateways that wrap a Claude-family model behind a synthesized tool call are parsed correctly instead of yielding silently empty insights.
The briefing token budget is configurable (INSIGHTS_MAX_TOKENS, default raised so full briefings stop truncating), and a reply that was cut off mid-JSON now says exactly that instead of a generic parse error. When the number-grounding check withholds a briefing, the card now explains why instead of pretending nothing was generated. The provider test button distinguishes "the endpoint rejected the request" from "could not reach the endpoint".
Sleep repair follow-up: re-syncing a history whose sleep rows predate the stable segment keys could erase those nights — the old rows were swept while their replacements collided with a second uniqueness rule and were silently dropped. Re-imports now recognise such rows by their natural identity and migrate them in place: fresh value, new key, restored if deleted. A full sync after updating heals any history affected.
The dashboard now refetches when its tab regains focus, so a tile-selection change made elsewhere shows up immediately instead of after the next poll.
v1.28.27 — Runs on CPUs without AVX2
Self-hosts on older x86-64 CPUs — Celeron/Atom-class NAS boxes, pre-2013 Xeons, and VMs that mask newer CPU flags — crashed in a restart loop since the document renderer arrived: the bundled rasterizer uses AVX2 instructions, and a CPU without them kills the whole process the first time a thumbnail or a scanned-PDF render runs. The renderer is now gated on a one-time CPU-feature check: on unsupported hardware, thumbnails and scanned-PDF rasterization simply switch off (the vault shows type icons; PDFs are read as text) while everything else runs normally. NATIVE_CANVAS=off|on overrides the detection if ever needed. Reported by a self-hoster with kernel traps in hand — thanks, that made it a same-day fix.
v1.28.26 — Internal restructuring for maintainability
No user-facing change. Eight of the largest source files were split along their natural seams into focused modules — mood analytics calculators, the insight status-invalidation machinery, feature extraction blocks, the doctor report's types and helpers, the Google Health mapping layer, the coach chat bubbles and read-aloud controls, the Telegram webhook handlers (the route now holds only auth and dispatch), and the coach snapshot's cache, series helpers and largest per-metric blocks. Every move is verbatim with stable import paths; behavior is pinned by the full test suite.
v1.28.25 — Every integration held to the same standard
A platform-wide hardening pass: the failure classes found live this week were hunted down across every integration, not just where they first surfaced.
Fitbit carried the same silent wedge fixed for Google Health — a soft-deleted reading permanently blocked its own re-import, hidden behind a comment describing a database index that never existed. Fixed the same way: a re-import revives the deleted row in place.
Sleep segments now keep stable identities across re-scoring for Withings, WHOOP, Polar and Oura — previously a source refining a night could duplicate or orphan its stage rows, quietly inflating the night's total. Each sync now also sweeps rows a re-score left behind, so already-affected nights heal themselves as they are re-read.
One platform rule for deleted rows: a reading owned by a connected source comes back when the source reports it again — across Withings, WHOOP, Oura, Polar, Nightscout, the mood webhook, CSV re-imports and Apple Health day totals. Deleting an Apple Health sample in the Health app still sticks, as the paired client expects. Import counts and per-entry statuses now tell the truth in every one of these paths.
Google Health lifecycle: an expired connection no longer lets the hourly sync stamp success while importing nothing; history backfills and the sleep repair only mark themselves done after a clean pass, so a transient error now retries instead of silently leaving a gap; and a failed database write holds the sync watermark so the affected window is re-fetched.
Scale: the doctor report reads only the columns and sections it renders; exports and backups read the measurement table in pages; long-window charts for sensor-dense metrics aggregate per day in the database beyond 90 days; blood-pressure pairing, achievement tallies and several type scans moved from in-memory loops to the database. A year of continuous sensor data can no longer stall or crash a request.
v1.28.24 — A deleted Google reading no longer blocks its re-import
A Google Health reading that had been soft-deleted could never be imported again: the sync treated the deleted row as absent and planned a fresh insert, but the database's uniqueness rule still saw the deleted row and silently dropped the insert — every sync, forever. A self-hoster's step days were stuck exactly this way. The sync now recognises the deleted row and revives it in place with the freshly fetched value: Google remains the source of truth for its own readings, so a re-import deliberately brings a deleted one back. To remove Google data permanently, disconnect the integration.
v1.28.23 — Sleep disagreement visible on the dashboard; Apple Health medication groundwork
When two sources disagree about last night's sleep — a sleep mat's time in bed against a watch's time asleep — the dashboard sleep tile now carries a small marker next to the value. The tooltip names each source and its total, so a surprising number explains itself instead of silently picking a side. The same marker backs the sleep panel; the shown total is unchanged either way.
Groundwork for importing medications and their taken doses from Apple Health (iOS 26+): the server now accepts medications mirrored from an external source and dose events carrying a stable external identity, imported idempotently — re-syncs never duplicate. A mirrored medication is source-exclusive, so an imported dose can never double-count against a manually logged one. The matching client support arrives with a future app update.
Hardening: the doctor-report aggregation and PDF charts now compute their ranges with loops instead of spread calls, so a report over a sample-dense year (per-beat heart rate, sensor glucose) cannot overflow the call stack — the same failure class fixed for the Google Health full sync.
v1.28.22 — Full sync survives dense histories; quieter document chrome
A full Google Health sync no longer fails on sample-dense accounts. Collecting the rollup bookkeeping for a multi-year heart-rate history overflowed the call stack, which aborted the metrics pass mid-cycle — exactly on the accounts with the most data. Reported by a self-hoster whose sync log made the diagnosis immediate; the new sync diagnostics from the previous release confirmed every other read on that account healthy.
The documents vault gets quieter chrome: the "read by AI" tag no longer takes its own line on each card — a small glyph rides inline on the date-and-size row instead. The document view drops the status bar under the preview entirely; when reading is automatic and nothing needs review, the content starts straight with the document's fields.
v1.28.21 — Sleep history heals itself; plateau context; sync diagnostics
Three follow-ups to the recent sleep fix, plus better Google Health diagnostics.
Nights that were stored twice before the sleep fix now repair themselves: a one-time background pass re-reads each connected account's Google Health sleep history and collapses every re-scored night to its true total. No operator action needed — it runs once after the update and marks itself done.
The sleep night read now reports when two sources disagree about the same night (say, a sleep mat's ten hours in bed against a watch's seven and a half asleep). The served total is unchanged — the disagreement rides along as an annotation so clients can mark the number instead of silently picking a side.
A GLP-1 medication whose weight has plateaued on the current dose now says so where the estimated drug level is shown — a short, factual note with the observed change over the window, next to the curve in the efficacy view and in Insights. It states the association and nothing more.
Google Health sync diagnostics: a daily-totals read that returns rows but imports nothing now says so in the sync log, distinguishing an empty answer from one the importer could not read — and the connection test's structure probe reports the raw response field names when a type parses to zero, so a per-account naming drift is visible from a single report.
v1.28.20 — Estimated drug level where you read efficacy
The estimated active-substance curve for a GLP-1 medication — the modelled drug level from your logged doses — now also appears in the efficacy view and in the Insights medication overview, not only under the injection tab. It is the same estimate shown there, surfaced next to the efficacy readouts so the drug level and its effect sit side by side. Non-GLP-1 medications are unaffected.