Merge feat/w-d-mental-wellbeing into release/v1.27.6

The Vorsorge card resolves both rules at once: the action button keeps
its constant look in every state (v1.27.5), and a screening reminder's
action reads 'start the check-in' instead of the numeric measure
wording (this wave).
This commit is contained in:
Marc-André Bombeck committed 2026-07-05 15:27:44 +02:00
commit f1e5df52ab
28 files changed
+657 -886

No files matched your search

@@ -14,7 +14,7 @@ import {
describe("measurementReminderTypeEnum (V3 — completeness)", () => {
const allowed = measurementReminderTypeEnum.options;
it("exposes the full active set: vitals + body composition", () => {
it("exposes the full active set: vitals + body composition + screenings", () => {
expect(new Set(allowed)).toEqual(
new Set([
// Vitals
@@ -34,10 +34,22 @@ describe("measurementReminderTypeEnum (V3 — completeness)", () => {
"TOTAL_BODY_WATER",
"VISCERAL_FAT",
"BODY_MASS_INDEX",
// v1.27.6 — plannable mental-wellbeing screenings
"PHQ9_SCORE",
"GAD7_SCORE",
]),
);
});
it("accepts a plannable screening reminder with the 4-week default cadence", () => {
const parsed = createMeasurementReminderSchema.safeParse({
label: "PHQ-9 Check-in",
measurementType: "PHQ9_SCORE",
intervalDays: 28,
});
expect(parsed.success).toBe(true);
});
it("accepts a newly-added body-composition type on create", () => {
const parsed = createMeasurementReminderSchema.safeParse({
label: "Körperzusammensetzung messen",
@@ -470,6 +470,24 @@ describe("measurement validation", () => {
}
});
it("rejects a COMPUTED-attributed screener score row on a client write", () => {
// v1.27.6 — the mental-wellbeing screener projects each completed
// administration onto a COMPUTED-source PHQ9_SCORE / GAD7_SCORE row
// (the RECOVERY_SCORE precedent). COMPUTED is not client-attributable,
// so a client can never mint a server-owned score trend point.
for (const type of ["PHQ9_SCORE", "GAD7_SCORE"]) {
const parsed = createMeasurementSchema.safeParse({
type,
value: 5,
measuredAt: "2026-06-02T12:00:00Z",
source: "COMPUTED",
});
expect(parsed.success, `expected COMPUTED ${type} to be rejected`).toBe(
false,
);
}
});
it("accepts the client-writable sources MANUAL and APPLE_HEALTH", () => {
for (const source of ["MANUAL", "APPLE_HEALTH"]) {
const parsed = createMeasurementSchema.safeParse({
+28 -1
View File
@@ -56,17 +56,44 @@ export const measurementReminderTypeEnum = z
"TOTAL_BODY_WATER",
"VISCERAL_FAT",
"BODY_MASS_INDEX",
// v1.27.6 — the mental-wellbeing screeners become plannable Vorsorge
// items. Not a passive wearable score: a screening is an active "go and
// do it" action exactly like stepping on the scale. Auto-resolves from
// the server-owned COMPUTED PHQ9_SCORE / GAD7_SCORE row the assessment
// route writes on completion. Gated on the opt-in mentalHealth module in
// the dispatcher (see `moduleForMeasurementType`).
"PHQ9_SCORE",
"GAD7_SCORE",
])
.meta({
id: "MeasurementReminderType",
description:
"Auto-resolve target metric (vitals + body-composition family). BP resolves on BLOOD_PRESSURE_SYS (the SYS row is the 'a BP was measured' sentinel). Omit for a free-text Vorsorge that resolves only on a manual satisfy or a matching lab result.",
"Auto-resolve target metric (vitals + body-composition family + the PHQ-9 / GAD-7 screenings). BP resolves on BLOOD_PRESSURE_SYS (the SYS row is the 'a BP was measured' sentinel); a screening reminder resolves on the server-written PHQ9_SCORE / GAD7_SCORE row when a check-in completes. Omit for a free-text Vorsorge that resolves only on a manual satisfy or a matching lab result.",
});
export type MeasurementReminderType = z.infer<
typeof measurementReminderTypeEnum
>;
/**
* v1.27.6 — the reminder types that are SCREENINGS, not numeric readings.
* Every surface with a per-reminder primary action branches on this: a
* screening routes to the check-in page (`/mental-wellbeing`) instead of
* opening the numeric MeasurementForm — a score is never typed in, it
* falls out of a completed test (which then auto-satisfies the reminder).
*/
export const SCREENING_REMINDER_TYPES: ReadonlySet<string> = new Set([
"PHQ9_SCORE",
"GAD7_SCORE",
]);
/** Whether a reminder's `measurementType` targets a screening check-in. */
export function isScreeningReminderType(
type: string | null | undefined,
): boolean {
return type != null && SCREENING_REMINDER_TYPES.has(type);
}
/**
* RFC-5545 RRULE guard. The recurrence engine parses it; here we only
* bound the length and require the `FREQ=` token so a typo doesn't reach