Skip to content

Releases: MBombeck/HealthLog

v1.28.31 — heart-rate history keeps its daily shape

Choose a tag to compare

@MBombeck MBombeck released this 12 Jul 10:19
14b61e1

Long-term retention for the dense intra-day signals (heart rate, HRV, blood oxygen) now folds to hourly means instead of a single daily mean. The last 14 days keep every raw sample as before; older days keep up to 24 points — enough to see how a day actually went, at roughly nine thousand rows per metric per year instead of an unbounded raw stream.

Because the old fold soft-deleted rather than erased, a one-time rebuild reconstructs the hourly shape for every already-folded day from the retained raw samples, then retires the old daily row in the same transaction — no reader can ever double-count a day. The rebuild runs automatically once per installation after the update; days whose raw samples are no longer available keep their daily mean. Daily min/max/mean statistics captured at fold time stay untouched throughout.

v1.28.30 — the daily briefing stops silently skipping days

Choose a tag to compare

@MBombeck MBombeck released this 12 Jul 09:20
85267f1

The recurring "no briefing today" had a chain of causes, now closed end to end. Every failure path in the nightly generation names its cause in the logs (several classes previously failed without a trace). A failed night retries itself about forty-five minutes later instead of waiting for the next night. A cached insight payload that carries no briefing no longer counts as fresh — previously it could satisfy the daily window and even the unchanged-data check, so a once-stripped briefing could block its own regeneration indefinitely. And the briefing card now reports persistently why a briefing is absent, with a retry that always generates.

Documentation corrections, verified against the code: Withings credentials are per-user in the app (the documented environment-variable path never existed — and the webhook secret is now actually passed through in the compose file); the classic Fitbit portal no longer accepts new registrations (new users: Google Health); the backup-restore example no longer generates a fresh encryption key (which could never decrypt an existing backup) and uses the invocation that works in the production image; the certificate-pinning guide now describes the real model (two CA-level pins, leaf renewals need no app build); scaling and admin-endpoint references corrected.

v1.28.29 — Dashboard edits show up on the way back; one scrollbar again

Choose a tag to compare

@MBombeck MBombeck released this 11 Jul 10:16
14a5173

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

Choose a tag to compare

@MBombeck MBombeck released this 11 Jul 08:52
16f7869

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

Choose a tag to compare

@MBombeck MBombeck released this 11 Jul 08:15
132ede8

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

Choose a tag to compare

@MBombeck MBombeck released this 11 Jul 06:55
da0e509

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

Choose a tag to compare

@MBombeck MBombeck released this 11 Jul 00:05
4303dda

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

Choose a tag to compare

@MBombeck MBombeck released this 10 Jul 22:46
1085a1e

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

Choose a tag to compare

@MBombeck MBombeck released this 10 Jul 22:28
1c0f91a

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

Choose a tag to compare

@MBombeck MBombeck released this 10 Jul 21:28
755da41

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.