fix(apple-health): show a written-back type's newest reading on its row

A reading entered here goes to Apple Health as well, and the quiet
verdict already counted it, but the row still showed the newest reading
that arrived from the phone, so a type entered two minutes ago read one
day old. The server now resolves lastSeenAt to the newer of the two for
the types the app writes back, and the row and the verdict agree.

Refs #1247
This commit is contained in:
Marc-André Bombeck committed 2026-10-20 19:47:07 +02:00
1 parent a0e725556b
commit 628f37bc62
4 files changed
+19 -8

No files matched your search

+3 -1
View File
@@ -77130,7 +77130,9 @@ components:
glucose while their entry is `bidirectional` or `writeOnly` and enabled): when the newest reading of the
type was entered here rather than arriving from Apple Health, or null when none was. Such readings go to the
phone and never come back as an arrival, so `stale` already counts them: the type is quiet only when both
this and `lastSeenAt` are. Show it as the server sends it; do not recompute `stale`."
are. Since v1.43.9 `lastSeenAt` on such a type is the newer of this and the newest reading from Apple
Health, because a reading entered here is in Apple Health too; show both as the server sends them and do not
recompute `stale`."
anyOf:
- type: string
format: date-time
@@ -406,6 +406,9 @@ describe("GET /api/integrations/healthkit — sync health", () => {
expect(entries.WEIGHT.lastEnteredHereAt).toBe(
enteredHereAt.toISOString(),
);
// The reading entered here is in Apple Health too, so it is the type's
// newest reading: the row and the verdict read the same instant.
expect(entries.WEIGHT.lastSeenAt).toBe(enteredHereAt.toISOString());
// A read-only type keeps its own verdict and carries no such field.
expect(entries.PULSE.stale).toBe(true);
expect(entries.PULSE).not.toHaveProperty("lastEnteredHereAt");
+12 -6
View File
@@ -342,21 +342,27 @@ export function classifyMetricFreshness(
return samples.map((sample) => {
// A type the app writes back to the phone (#1247): what is entered here
// goes to Apple Health and never arrives back, so a person who keeps
// their readings here would otherwise watch the type go quiet. The newer
// of the two is what says whether the type is still in use.
// their readings here would otherwise watch the type go quiet. That
// reading is in Apple Health too, so the newer of the two is the type's
// newest reading: it is what the row shows and what the verdict reads.
const written = enteredHere?.has(sample.type)
? { lastEnteredHereAt: enteredHere.get(sample.type) ?? null }
: {};
if (verdict !== "fresh" || SPARSE_METRIC_TYPES.has(sample.type)) {
return { ...sample, ...written, stale: false };
}
const seen = newest(
toMillis(sample.lastSeenAt),
toMillis(written.lastEnteredHereAt),
);
return {
const resolved = {
...sample,
...written,
lastSeenAt:
seen != null ? new Date(seen).toISOString() : sample.lastSeenAt,
};
if (verdict !== "fresh" || SPARSE_METRIC_TYPES.has(sample.type)) {
return { ...resolved, stale: false };
}
return {
...resolved,
stale: seen != null && nowMs - seen > METRIC_QUIET_AFTER_MS,
};
});
+1 -1
View File
@@ -293,7 +293,7 @@ const appleHealthMetricFreshnessEntry = metricFreshnessEntry
.nullable()
.optional()
.describe(
"v1.43.7 — present only on a type the app writes back to Apple Health (weight, blood pressure, blood glucose while their entry is `bidirectional` or `writeOnly` and enabled): when the newest reading of the type was entered here rather than arriving from Apple Health, or null when none was. Such readings go to the phone and never come back as an arrival, so `stale` already counts them: the type is quiet only when both this and `lastSeenAt` are. Show it as the server sends it; do not recompute `stale`.",
"v1.43.7 — present only on a type the app writes back to Apple Health (weight, blood pressure, blood glucose while their entry is `bidirectional` or `writeOnly` and enabled): when the newest reading of the type was entered here rather than arriving from Apple Health, or null when none was. Such readings go to the phone and never come back as an arrival, so `stale` already counts them: the type is quiet only when both are. Since v1.43.9 `lastSeenAt` on such a type is the newer of this and the newest reading from Apple Health, because a reading entered here is in Apple Health too; show both as the server sends them and do not recompute `stale`.",
),
})
.meta({