From e657bf928eb0ddeb2347bf15fc9f4a7731b7097e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andr=C3=A9=20Bombeck?= Date: Sat, 25 Jul 2026 09:41:58 +0200 Subject: [PATCH] =?UTF-8?q?chore(release):=20v1.32.31=20=E2=80=94=20the=20?= =?UTF-8?q?integrations=20page=20says=20what=20is=20actually=20happening?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHANGELOG.md | 41 +++++++++++++++++++++++++++ docs/api/openapi.yaml | 64 +++++++++++++++++++++++++++++++++++++++++-- package.json | 2 +- public/sw.js | 2 +- 4 files changed, 105 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a313eb314..111f0f5d1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,47 @@ ## [Unreleased] +## [1.32.31] — 2026-07-25 + +The integrations page could tell you a connection was failing. It could not +tell you a connection had stopped trying. Those look the same from the outside: +the page keeps showing whatever error was recorded last, and it keeps showing +it for as long as nobody looks, because nothing anywhere read how old the last +attempt actually was. This release adds that reading, and every status on the +page now comes from it. + +- **A sync that has stopped running is labelled as stopped.** When a connection + has not even attempted to sync for a day, it says so, with how long it has + been. Previously it kept displaying the last error it managed to record, which + reads like something that is still retrying. +- **Per data type, when a value last arrived.** Each connected card now opens a + short list: every data type that connection has ever delivered, and how long + ago the newest one came in. A type that has gone quiet for two weeks while the + rest of the connection is healthy is called out, which is the shape a single + broken pipe takes: one revoked permission, one endpoint that started returning + nothing, while the sync itself keeps reporting success. Types the connection + has never delivered are simply not listed. Nothing is invented. +- **Apple Health has a staleness threshold.** Any timestamp at all used to paint + the green "data received" badge, so a phone that stopped delivering three + weeks ago looked exactly like one that delivered this morning. After a week of + silence the card now says so and points you at the app's Apple Health + permissions. It also lists the same per data type detail as the others. +- **A configured connection with no card of its own now appears.** If your + account has a connection set up that the page has no card for, it gets a plain + status row instead of being left off entirely. That is a structural fix: a + connection can no longer be running, failing and invisible at the same time. +- **The status colours mean one consistent thing.** Red is now reserved for the + cases your own action fixes, which is reconnecting or resuming. An upstream + outage or a rate limit carries the amber tone instead, because clicking + reconnect against a server error has never helped anyone. Every card uses the + same mapping. Three of them used to disagree with each other on the same page. +- **Nightscout reads the same status source as everything else.** It was the + last card fetching its own status separately, which is why it could show a + different colour for the same condition. + +The assistant tools report the new verdict too, so asking why your data looks +stale gets the real answer instead of "connected". + ## [1.32.30] — 2026-07-25 Height was the last value still asked for in centimetres regardless of the diff --git a/docs/api/openapi.yaml b/docs/api/openapi.yaml index 8a5654572..1fab50617 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.32.30 + version: 1.32.31 description: >- Self-hosted personal-health-tracking PWA — public API surface for the iOS native client and external ingest. @@ -32471,12 +32471,21 @@ components: pattern: ^(?:(?:\d\d[2468][048]|\d\d[13579][26]|\d\d0[48]|[02468][048]00|[13579][26]00)-02-29|\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\d|30)|(?:02)-(?:0[1-9]|1\d|2[0-8])))T(?:(?:[01]\d|2[0-3]):[0-5]\d(?::[0-5]\d(?:\.\d+)?)?(?:Z|([+-](?:[01]\d|2[0-3]):[0-5]\d)))$ - type: "null" description: When HealthKit last synced for this user; null when never (and always null on the PATCH echo). + syncHealth: + description: Present on the GET read; omitted from the PATCH echo. + $ref: "#/components/schemas/SyncHealth" + metricFreshness: + description: Per-metric-type freshness for the `APPLE_HEALTH` source. Present on the GET read; omitted from the PATCH + echo. + type: array + items: + $ref: "#/components/schemas/MetricFreshnessEntry" required: - entries - lastSyncedAt additionalProperties: false description: "The resolved HealthKit integration config: the default metric set merged with the user's stored per-metric - overrides." + overrides, plus the sync-health verdict and per-metric freshness." HealthKitEntry: type: object properties: @@ -32503,6 +32512,57 @@ components: - enabled additionalProperties: false description: One resolved HealthKit metric mapping (defaults merged with the user's stored overrides). + SyncHealth: + type: object + properties: + verdict: + type: string + enum: + - fresh + - stale + - stalled + - failing + - reauth_required + - parked + - pending_first_sync + - disconnected + description: "Liveness verdict. For Apple Health: `fresh` when data arrived within the last 7 days, `stale` when it did + not, `pending_first_sync` when none ever arrived." + since: + anyOf: + - type: string + format: date-time + pattern: ^(?:(?:\d\d[2468][048]|\d\d[13579][26]|\d\d0[48]|[02468][048]00|[13579][26]00)-02-29|\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\d|30)|(?:02)-(?:0[1-9]|1\d|2[0-8])))T(?:(?:[01]\d|2[0-3]):[0-5]\d(?::[0-5]\d(?:\.\d+)?)?(?:Z|([+-](?:[01]\d|2[0-3]):[0-5]\d)))$ + - type: "null" + description: The instant that triggered the verdict; null when none did. + required: + - verdict + - since + additionalProperties: false + description: Server-resolved sync-health verdict. The single source of liveness truth — `lastSyncedAt` alone cannot + express 'this pipe stopped delivering'. + MetricFreshnessEntry: + type: object + properties: + type: + type: string + description: The measurement type (e.g. `RESPIRATORY_RATE`), or `WORKOUTS` for the workout leg. + lastSeenAt: + type: string + format: date-time + pattern: ^(?:(?:\d\d[2468][048]|\d\d[13579][26]|\d\d0[48]|[02468][048]00|[13579][26]00)-02-29|\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\d|30)|(?:02)-(?:0[1-9]|1\d|2[0-8])))T(?:(?:[01]\d|2[0-3]):[0-5]\d(?::[0-5]\d(?:\.\d+)?)?(?:Z|([+-](?:[01]\d|2[0-3]):[0-5]\d)))$ + description: Newest recorded reading for this type from this source. + stale: + type: boolean + description: This type has gone quiet while the source around it reads healthy — the dead-pipe signature (e.g. a single + revoked HealthKit permission). Only ever true when the verdict is `fresh`. + required: + - type + - lastSeenAt + - stale + additionalProperties: false + description: Per-metric-type last-seen timestamp with the server-computed staleness flag. Only types that have actually + delivered appear — absence is absence, never an invented row. HealthKitConfigPatchEnvelope: type: object properties: diff --git a/package.json b/package.json index ea37d0e0c..315591f8c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "healthlog", - "version": "1.32.30", + "version": "1.32.31", "description": "Self-hosted personal-health-tracking PWA with Withings integration, AI insights, and doctor-report PDF export.", "license": "PolyForm-Noncommercial-1.0.0", "homepage": "https://healthlog.dev", diff --git a/public/sw.js b/public/sw.js index 88628ec5e..0c44f3498 100644 --- a/public/sw.js +++ b/public/sw.js @@ -36,7 +36,7 @@ try { // v1.4.38.4 → v1.4.42. Do not hand-edit; bump `package.json` and rebuild. const CACHE_VERSION = (typeof self !== "undefined" && self.__APP_VERSION__) || - /* @sw-version-fallback */ "v1.32.30"; + /* @sw-version-fallback */ "v1.32.31"; const STATIC_CACHE = `healthlog-static-${CACHE_VERSION}`; const PAGE_CACHE = `healthlog-pages-${CACHE_VERSION}`; // v1.18.6 — read-only data cache for a curated allowlist of safe GET `/api/*`