feat(coach): evidence-based cadence suggestions with one-tap in-chat reminder

The Coach can append a ---SUGGEST-REMINDER--- sentinel to propose a
measurement cadence (weight daily, the ESH/AHA 7-2-2 home-BP protocol,
structured glucose). The server resolves the cadence from a closed catalog
and emits an additive 'suggestion' SSE frame the chat renders as a one-tap
action card; accepting it creates a MeasurementReminder with origin COACH
through the same engine the Vorsorge surface uses (server-authoritative
schedule, finite course window self-expires via endsOn).

Non-naggy by construction: a frequency-cap cooldown, dismissal memory, a
master opt-out under Coach settings, and an explicit 'I measure enough'
stop path, all in the coach-prefs blob. Triple dedup at suggest, render,
and create so a cadence is never double-suggested or double-created, and
suggestions for a disabled module never surface. RHR/HRV stay passive --
the Coach surfaces the baseline, it never nudges to 'go measure' them.

The reminder DTO now echoes origin and endsOn for the iOS client.
This commit is contained in:
Marc-André Bombeck committed 2026-06-16 21:17:51 +02:00
1 parent b6796eee96
commit 12f4bc24e8
29 files changed
+1538 -9

No files matched your search

@@ -296,6 +296,42 @@ export function CoachPrefsSection({ isAuthenticated }: CoachPrefsSectionProps) {
})}
</ul>
</div>
{/* v1.18.1 (Workstream C) — cadence-suggestion opt-out. The
Coach offers an occasional, evidence-based reminder to
measure a metric more regularly; this is the master switch
for that surface. Off ⇒ no suggestion cards ever surface. */}
<div className="flex items-center justify-between gap-3">
<div className="flex flex-col gap-1">
<Label
htmlFor="coach-prefs-reminder-suggestions"
className="cursor-pointer text-xs font-medium"
>
{t("coach.settings.reminderSuggestionsLabel")}
</Label>
<p className="text-muted-foreground text-[11px] leading-relaxed">
{t("coach.settings.reminderSuggestionsDescription")}
</p>
</div>
<Switch
id="coach-prefs-reminder-suggestions"
data-slot="coach-prefs-reminder-suggestions"
checked={draft.reminderSuggestions?.enabled ?? true}
onCheckedChange={(next) =>
setDraft((prev) => ({
...prev,
reminderSuggestions: {
enabled: next,
stopped: prev.reminderSuggestions?.stopped ?? false,
dismissedCadences:
prev.reminderSuggestions?.dismissedCadences ?? [],
lastSuggestedAt:
prev.reminderSuggestions?.lastSuggestedAt ?? null,
},
}))
}
/>
</div>
</div>
)}
<div className="mt-4 flex justify-end pl-7">