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