fix(healthkit): count a type as arrived only when the batch brought something new

A batch that carried a type with duplicates only moved its
lastReceivedAt, lastTrigger and lastBackgroundAt, so a type whose live
delivery had stalled kept reading as freshly arrived for as long as a
replay, a retry or an older client re-sent its old samples.

The arrival ledger now moves those stamps only for a type the batch
brought at least one new or changed row for. An unchanged stats:
re-post and an hour posted again with its stored value count as
duplicates here; the wire keeps answering updated for them. A
duplicates-only type stamps the new last_seen_duplicate_at column on
its existing row and nothing else. The intake bulk route's dose entry
follows the same rule through the shared recorder. lastSyncedAt and
healthKitLastSyncedAt are unchanged.

GET /api/integrations/healthkit serves the stamp additively as
metricFreshness[].lastSeenDuplicateAt, and the Apple Health card names a
re-send that came after the last arrival.

Refs #1259
This commit is contained in:
Marc-André Bombeck committed 2026-10-22 10:34:45 +02:00
1 parent 703c92c776
commit 6a2247e422
25 files changed
+547 -84

No files matched your search

@@ -449,6 +449,32 @@ describe("<AppleHealthCard> — per-type arrivals (#1173)", () => {
expect(out).toContain("Last new value 3 d ago");
});
// #1259 — a duplicates-only batch no longer moves the arrival; the card
// says the phone kept sending the type without hiding when it last brought
// something.
it("names a later re-send with nothing new beside the last arrival", () => {
const out = html({
...base,
lastReceivedAt: hoursAgo(20),
lastTrigger: "foreground",
lastNewSampleAt: hoursAgo(20),
lastSeenDuplicateAt: hoursAgo(1),
});
expect(out).toContain("Last received 20 h ago");
expect(out).toContain("Last re-sent with nothing new 1 h ago");
});
it("does not name a re-send older than the last arrival", () => {
const out = html({
...base,
lastReceivedAt: hoursAgo(1),
lastTrigger: "foreground",
lastNewSampleAt: hoursAgo(1),
lastSeenDuplicateAt: hoursAgo(5),
});
expect(out).not.toContain("Last re-sent");
});
// v1.42.4 — opening the app to look starts a foreground sync; the earlier
// background arrival must stay visible beside it.
it("keeps the last background arrival visible after a later foreground one", () => {
@@ -192,7 +192,8 @@ export function ArrivalDetail({
: "settings.appleHealth.delivery.trigger.unknown",
),
];
// Only worth a mention when the last arrival brought duplicates alone.
// Only worth a mention on a row last written before duplicates stopped
// counting as an arrival (#1259); since then the two move together.
if (entry.lastNewSampleAt && entry.lastNewSampleAt !== entry.lastReceivedAt) {
parts.push(
t("settings.appleHealth.freshness.lastNewValue", {
@@ -200,6 +201,18 @@ export function ArrivalDetail({
}),
);
}
// The phone kept re-sending the type after its last new value: the
// arrival above stays put, and this says the type is still being sent.
if (
entry.lastSeenDuplicateAt &&
Date.parse(entry.lastSeenDuplicateAt) > Date.parse(entry.lastReceivedAt)
) {
parts.push(
t("settings.appleHealth.freshness.lastDuplicate", {
relative: relative(entry.lastSeenDuplicateAt),
}),
);
}
// 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 (