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