Merge pull request #742 from MBombeck/fix/backfill-taken-at-slot

Record a backfilled dose at its slot's own time
This commit is contained in:
Marc-André Bombeck authored and GitHub committed 2026-08-08 09:53:55 +02:00
commit 3510043eba
5 files changed
+52 -11

No files matched your search

+9
View File
@@ -2,6 +2,15 @@
## [Unreleased]
### Fixed
- Marking an older missed dose as taken from the intake history now records
the dose at the slot's own date and time. Marking Tuesday 09:00 from a
Thursday evening used to land on the right slot but stamp the intake with
the current moment; the recorded time now matches the claim being made. The
optimistic paint had promised this all along, and the skipped-to-taken flip
in the same view already behaved this way.
## [1.37.1] — 2026-08-08
### Added
+1 -1
View File
@@ -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.37.0";
/* @sw-version-fallback */ "v1.37.1";
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/*`
@@ -2,6 +2,7 @@ import { describe, expect, it } from "vitest";
import {
applyOptimisticSlotMark,
slotMarkRequestBody,
complianceFromLedger,
formatSlotDelta,
groupLedgerByDay,
@@ -195,3 +196,23 @@ describe("applyOptimisticSlotMark", () => {
expect(next.rows[0].status).toBe("taken_late");
});
});
describe("slotMarkRequestBody", () => {
const row = { at: "2026-08-04T07:00:00.000Z" };
it("anchors takenAt on the slot instant, not on now", () => {
const body = slotMarkRequestBody(row, "taken");
expect(body).toEqual({
skipped: false,
scheduledFor: "2026-08-04T07:00:00.000Z",
takenAt: "2026-08-04T07:00:00.000Z",
});
});
it("keeps the skipped arm free of takenAt", () => {
expect(slotMarkRequestBody(row, "skipped")).toEqual({
skipped: true,
scheduledFor: "2026-08-04T07:00:00.000Z",
});
});
});
@@ -204,3 +204,18 @@ export function applyOptimisticSlotMark(
});
return { ...payload, rows };
}
/**
* Request body for marking a ledger slot from the Verlauf. The taken arm
* anchors `takenAt` on the slot instant: marking Tuesday 09:00 from a
* Thursday evening records a dose taken Tuesday 09:00, which is the claim
* the person is making. The optimistic paint above already promised this.
*/
export function slotMarkRequestBody(
row: Pick<LedgerRow, "at">,
action: "taken" | "skipped",
): Record<string, unknown> {
return action === "skipped"
? { skipped: true, scheduledFor: row.at }
: { skipped: false, scheduledFor: row.at, takenAt: row.at };
}
@@ -106,6 +106,7 @@ import {
formatSlotDelta,
groupLedgerByDay,
isSlotActionable,
slotMarkRequestBody,
type LedgerPayload,
type LedgerRow,
} from "@/components/medications/dose-history-ledger-compute";
@@ -273,18 +274,13 @@ export function DoseHistoryLedger({
try {
// Pin the displayed slot so the server marks THAT dose (the canonical
// slot upsert), not a now-snap to the nearest one. The POST returns
// the created event; its id drives the Undo affordance (the same
// soft-delete route the cards use).
// slot upsert), not a now-snap to the nearest one — and anchor
// `takenAt` on the same instant, matching the optimistic paint. The
// POST returns the created event; its id drives the Undo affordance
// (the same soft-delete route the cards use).
const created = await apiPost<{ id?: string } | undefined>(
`/api/medications/${medicationId}/intake`,
action === "skipped"
? { skipped: true, scheduledFor: row.at }
: {
skipped: false,
scheduledFor: row.at,
takenAt: new Date().toISOString(),
},
slotMarkRequestBody(row, action),
);
const eventId: string | undefined = created?.id;
toast.success(