feat(healthkit): record Apple Health dose arrivals and the last background arrival per type

Migration 0401 turns healthkit_type_syncs.type into text so the ledger can
hold MEDICATION_DOSE, and adds last_background_at (seeded from rows whose
latest arrival was a background one). POST /api/medications/intake/bulk
takes syncTrigger and records APPLE_HEALTH dose arrivals; the Apple Health
status read serves a MEDICATION_DOSE freshness entry and lastBackgroundAt
on every entry with arrival facts.
This commit is contained in:
Marc-André Bombeck committed 2026-10-15 15:33:30 +02:00
1 parent 6313c0174a
commit 03b24461ad
22 files changed
+352 -54

No files matched your search

@@ -133,7 +133,8 @@ function DeliveryDiagnostic({
* under which trigger, and when one last brought a new value. The newest
* sample's time on the right of the row says when the reading was TAKEN; this
* says when the server RECEIVED it, which is what tells a type that only
* arrives on "Sync all" from one the phone delivers by itself (#1173). Nothing
* arrives on "Sync all" from one the phone delivers by itself (#1173), plus
* the last background arrival when a later one came another way. Nothing
* renders when the server sent no arrival facts (the workouts row, or a failed
* ledger read), so absence is never painted as "not received".
*/
@@ -176,6 +177,18 @@ export function ArrivalDetail({
}),
);
}
// The last background arrival, when a later one came another way: opening
// the app to look would otherwise hide that the phone delivers by itself.
if (
entry.lastBackgroundAt &&
entry.lastBackgroundAt !== entry.lastReceivedAt
) {
parts.push(
t("settings.appleHealth.freshness.lastBackground", {
relative: relative(entry.lastBackgroundAt),
}),
);
}
return (
<span
data-testid="apple-health-arrival"
@@ -30,6 +30,8 @@ import { cn } from "@/lib/utils";
/** The pseudo-type carrying a source's newest workout. */
const WORKOUT_TYPE = "WORKOUTS";
/** The pseudo-type carrying Apple Health medication doses. */
const MEDICATION_DOSE_TYPE = "MEDICATION_DOSE";
/**
* Turn an unmapped enum name into something readable. `MEASUREMENT_TYPE_LABEL_KEYS`
@@ -68,6 +70,8 @@ export function MetricFreshnessList<
const label = (type: string): string => {
if (type === WORKOUT_TYPE)
return t("settings.integrationFreshness.workouts");
if (type === MEDICATION_DOSE_TYPE)
return t("settings.integrationFreshness.medicationDoses");
const key = MEASUREMENT_TYPE_LABEL_KEYS[type];
return key ? t(key) : humanise(type);
};