From f0a14ac0b4610deb3dd9e94ae96dfa7d857e8a2b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andr=C3=A9=20Bombeck?= Date: Sun, 14 Jun 2026 23:28:36 +0200 Subject: [PATCH] feat(measurements): surface that the entry timestamp is editable for backdating MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The add form already defaults the timestamp to now and accepts any earlier instant — the plausibility bound and the picker's max both block the future — but many users never notice they can change it. Add a one-line hint under the date/time field so logging a past reading is discoverable, and pin the manual-create contract with a guard asserting the single POST schema strips an externalId (the stats:-prefix overwrite stays batch-scoped). --- .../measurements/measurement-form.tsx | 6 ++++++ .../validations/__tests__/measurement.test.ts | 20 +++++++++++++++++++ 2 files changed, 26 insertions(+) diff --git a/src/components/measurements/measurement-form.tsx b/src/components/measurements/measurement-form.tsx index 6afe01da2..bce390b67 100644 --- a/src/components/measurements/measurement-form.tsx +++ b/src/components/measurements/measurement-form.tsx @@ -481,6 +481,12 @@ export function MeasurementForm({ aria-invalid={!!error || undefined} aria-describedby={errorDescriptor} /> + {/* v1.17.1 — surface that the timestamp is editable. The picker + already defaults to now and accepts any earlier instant; many + users never notice they can backdate a reading. */} +

+ {t("measurements.timestampBackdateHint")} +

diff --git a/src/lib/validations/__tests__/measurement.test.ts b/src/lib/validations/__tests__/measurement.test.ts index 983d019ba..5110c1e97 100644 --- a/src/lib/validations/__tests__/measurement.test.ts +++ b/src/lib/validations/__tests__/measurement.test.ts @@ -206,6 +206,26 @@ describe("measurement validation", () => { expect(parsed.data.deviceType).toBeUndefined(); } }); + + // v1.17.1 — the `stats:`-prefix overwrite contract is deliberately + // batch-scoped. The single manual POST must never accept an + // `externalId`, which would open a client-controlled overwrite vector + // on the `(userId, type, source, externalId)` unique key. The schema + // strips it (no passthrough), so the parsed value never carries one. + it("does not surface an externalId on the manual create schema (overwrite contract)", () => { + const parsed = createMeasurementSchema.safeParse({ + ...validBase, + type: "WEIGHT", + value: 82, + externalId: "stats:HKQuantityTypeIdentifierBodyMass:2026-04-27", + }); + expect(parsed.success).toBe(true); + if (parsed.success) { + expect( + (parsed.data as Record).externalId, + ).toBeUndefined(); + } + }); }); // v1.4.37 W7c — list-view "one row per day" mode for cumulative