feat(apple-health): show when each type last arrived and how

The Apple Health card listed each type's newest sample. A type the phone
only hands over on "Sync all" can carry a reading from this morning and
never have arrived in the background, so the sample time could not answer
whether a type flows on its own.

The batch route's per-type ledger now records, in one upsert per batch,
when each Apple Health type last arrived, the trigger that carried it
(including the new `manual`), and when a batch last brought a new or
updated sample of it. Duplicate-only re-sends move the arrival but keep the
last new value; scoped credentials and MANUAL rows write nothing, the same
rule as the sync checkpoint. A ledger failure leaves a warn line and never
fails the ingest.

`GET /api/integrations/healthkit` lays `lastReceivedAt`, `lastTrigger` and
`lastNewSampleAt` over each `metricFreshness` entry (null for a type no live
sync has carried, absent on the workouts entry and when the ledger read
failed), and the card shows them as a second line per type.

The per-type newest-sample read walks live types through
`listLiveMeasurementTypes` and probes each type's newest row instead of
grouping every live row of the source.
This commit is contained in:
Marc-André Bombeck committed 2026-10-08 09:22:49 +02:00
1 parent c8470cc1e6
commit ba99551faa
15 files changed
+976 -114

No files matched your search

@@ -91,8 +91,6 @@ const RELEASE_PENDING_PREFIXES: Record<string, string> = {
"environment.fields.europeanAqi": "v1.42 air-quality field label (#615)",
"environment.fields.uvIndexMax": "v1.42 air-quality field label (#615)",
"environment.fields.dustMax": "v1.42 air-quality field label (#615)",
"settings.appleHealth.freshness":
"v1.42 per-type HealthKit freshness list (#1173)",
"insights.workouts.manual.editTitle": "v1.42 manual workout edit (#1162)",
"insights.workouts.manual.editAction": "v1.42 manual workout edit (#1162)",
"insights.workouts.manual.updated": "v1.42 manual workout edit (#1162)",