From 2da6a192a7887c1677a1e1874eea2ade1c6cd5d3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andr=C3=A9=20Bombeck?= Date: Fri, 17 Jul 2026 10:47:36 +0200 Subject: [PATCH] feat(polish): default tracking modules on; illness backdate + course fixes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A clean install now defaults every tracking module on (weight, blood pressure, glucose, sleep, mood, medications, labs, documents, insights, mental wellbeing, cycle, illness, environment, workouts) so the app is usable without enabling each surface by hand. Only the two channels that open an egress or attack surface stay opt-in — the remote MCP endpoint and the micronutrient sync; the document AI-read and the daily-briefing push remain separately gated and off. The change touches defaults only: accounts with a saved preference resolve exactly as before, the new default applies solely where no preference exists. Drop the "typical range = median ± MAD" method caption from the vitals baseline tiles — it repeated across every tile and read as out of place at the top of the vitals surface. The dedicated bands and composite scores keep their method copy. Illness episodes: the capture sheet now carries an editable end date alongside the start, so a whole past episode can be recorded after the fact and an open-ended course can be resolved (or re-opened) later. The end is bounded to on-or-after the start. Fix the per-episode "how it progressed" card rendering an empty body when the engine returned a valid result with no notable findings — it now shows a calm, honest line instead of looking as though nothing was recorded. Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_018V8V9qapgHi7fhgtMJ8SkM --- messages/de.json | 8 ++- messages/en.json | 8 ++- messages/es.json | 8 ++- messages/fr.json | 8 ++- messages/it.json | 8 ++- messages/pl.json | 8 ++- .../illness/illness-correlation-card.tsx | 19 ++++++ src/components/illness/new-episode-sheet.tsx | 50 +++++++++++++-- src/components/illness/types.ts | 3 + .../insights/derived/vitals-dashboard.tsx | 9 ++- src/lib/modules/__tests__/gate.test.ts | 60 +++++++++--------- src/lib/modules/registry.ts | 63 ++++++++++--------- 12 files changed, 172 insertions(+), 80 deletions(-) diff --git a/messages/de.json b/messages/de.json index 3dfe3715f..aa4bcd595 100644 --- a/messages/de.json +++ b/messages/de.json @@ -7693,7 +7693,10 @@ "notePlaceholder": "Was erwähnenswert ist (verschlüsselt).", "saveError": "Die Episode konnte nicht gespeichert werden. Bitte erneut versuchen.", "parent": "Teil von", - "parentNone": "Nicht verknüpft" + "parentNone": "Nicht verknüpft", + "resolved": "Beendet am", + "resolvedHelp": "Leer lassen, solange die Episode noch andauert.", + "markOngoing": "Noch andauernd" }, "sheet": { "title": "{date} erfassen", @@ -7745,7 +7748,8 @@ "direction": { "above": "über deinem Üblichen", "below": "unter deinem Üblichen" - } + }, + "settled": "Während dieser Episode wich nichts Auffälliges von deinem üblichen Bereich ab." }, "insights": { "title": "Dein Krankheitsverlauf", diff --git a/messages/en.json b/messages/en.json index d789de5f6..af5e01480 100644 --- a/messages/en.json +++ b/messages/en.json @@ -7693,7 +7693,10 @@ "notePlaceholder": "Anything worth remembering (encrypted).", "saveError": "Could not save the episode. Please try again.", "parent": "Part of", - "parentNone": "Not linked" + "parentNone": "Not linked", + "resolved": "Ended on", + "resolvedHelp": "Leave empty while the episode is still ongoing.", + "markOngoing": "Still ongoing" }, "sheet": { "title": "Log {date}", @@ -7745,7 +7748,8 @@ "direction": { "above": "above your usual", "below": "below your usual" - } + }, + "settled": "Nothing notable stood out from your usual range during this episode." }, "insights": { "title": "Your illness history", diff --git a/messages/es.json b/messages/es.json index fdd912d82..09e524701 100644 --- a/messages/es.json +++ b/messages/es.json @@ -7693,7 +7693,10 @@ "notePlaceholder": "Algo que valga la pena recordar (cifrado).", "saveError": "No se pudo guardar el episodio. Inténtalo de nuevo.", "parent": "Parte de", - "parentNone": "Sin vincular" + "parentNone": "Sin vincular", + "resolved": "Finalizó el", + "resolvedHelp": "Déjalo vacío mientras el episodio siga en curso.", + "markOngoing": "Aún en curso" }, "sheet": { "title": "Registrar {date}", @@ -7745,7 +7748,8 @@ "direction": { "above": "por encima de lo habitual", "below": "por debajo de lo habitual" - } + }, + "settled": "Durante este episodio nada destacó respecto a tu rango habitual." }, "insights": { "title": "Tu historial de enfermedades", diff --git a/messages/fr.json b/messages/fr.json index ac8b3e0c8..a37d14cea 100644 --- a/messages/fr.json +++ b/messages/fr.json @@ -7693,7 +7693,10 @@ "notePlaceholder": "Ce qui mérite d'être noté (chiffré).", "saveError": "Impossible d'enregistrer l'épisode. Veuillez réessayer.", "parent": "Fait partie de", - "parentNone": "Non lié" + "parentNone": "Non lié", + "resolved": "Terminée le", + "resolvedHelp": "Laissez vide tant que l'épisode est en cours.", + "markOngoing": "Toujours en cours" }, "sheet": { "title": "Enregistrer {date}", @@ -7745,7 +7748,8 @@ "direction": { "above": "au-dessus de ton habituel", "below": "en dessous de ton habituel" - } + }, + "settled": "Rien de notable ne s'est écarté de ta plage habituelle pendant cet épisode." }, "insights": { "title": "Ton historique de maladies", diff --git a/messages/it.json b/messages/it.json index 32d7464c8..6dbf5208e 100644 --- a/messages/it.json +++ b/messages/it.json @@ -7693,7 +7693,10 @@ "notePlaceholder": "Qualcosa da ricordare (cifrato).", "saveError": "Impossibile salvare l'episodio. Riprova.", "parent": "Parte di", - "parentNone": "Non collegato" + "parentNone": "Non collegato", + "resolved": "Conclusa il", + "resolvedHelp": "Lascia vuoto finché l'episodio è ancora in corso.", + "markOngoing": "Ancora in corso" }, "sheet": { "title": "Registra {date}", @@ -7745,7 +7748,8 @@ "direction": { "above": "sopra il tuo solito", "below": "sotto il tuo solito" - } + }, + "settled": "Durante questo episodio nulla si è discostato in modo evidente dal tuo intervallo abituale." }, "insights": { "title": "La tua storia di malattie", diff --git a/messages/pl.json b/messages/pl.json index c5e8028ff..beb3947b1 100644 --- a/messages/pl.json +++ b/messages/pl.json @@ -7693,7 +7693,10 @@ "notePlaceholder": "Cokolwiek wartego zapamiętania (zaszyfrowane).", "saveError": "Nie udało się zapisać epizodu. Spróbuj ponownie.", "parent": "Część", - "parentNone": "Niepowiązane" + "parentNone": "Niepowiązane", + "resolved": "Zakończono", + "resolvedHelp": "Pozostaw puste, dopóki epizod nadal trwa.", + "markOngoing": "Nadal trwa" }, "sheet": { "title": "Zapisz {date}", @@ -7745,7 +7748,8 @@ "direction": { "above": "powyżej Twojej normy", "below": "poniżej Twojej normy" - } + }, + "settled": "Podczas tego epizodu nic istotnego nie odbiegało od twojego zwykłego zakresu." }, "insights": { "title": "Twoja historia chorób", diff --git a/src/components/illness/illness-correlation-card.tsx b/src/components/illness/illness-correlation-card.tsx index 4edec9d0b..852b7d9d4 100644 --- a/src/components/illness/illness-correlation-card.tsx +++ b/src/components/illness/illness-correlation-card.tsx @@ -57,6 +57,25 @@ function CorrelationBody({ value }: { value: IllnessCorrelationValue }) { // the engine on thin data — never a recovery claim, never a second gap. const sleep = value.sleepContext; + // The engine can return `ok` with NO findings — a mild episode where nothing + // strayed notably from the personal band, still active with no recovery gap, + // and no red flag. Without this the card body rendered empty under its "how + // it progressed" heading, reading as if nothing was recorded at all. Show a + // calm, honest line instead (retrospective, never a fabricated finding). + const hasContent = + value.redFlags.length > 0 || + gap !== null || + value.preOnset.length > 0 || + value.nadir.length > 0; + + if (!hasContent) { + return ( +

+ {t("illness.correlation.settled")} +

+ ); + } + return (
{value.redFlags.length > 0 ? ( diff --git a/src/components/illness/new-episode-sheet.tsx b/src/components/illness/new-episode-sheet.tsx index 0334548b3..9b14f8cbd 100644 --- a/src/components/illness/new-episode-sheet.tsx +++ b/src/components/illness/new-episode-sheet.tsx @@ -83,6 +83,10 @@ export function NewEpisodeSheet({ const [type, setType] = useState("INFECTION"); const [lifecycle, setLifecycle] = useState("ACUTE"); const [onset, setOnset] = useState(today); + // Resolved/end date — "" means the episode is still ongoing (open-ended). + // Backdating both onset and this end date lets a whole past episode be + // recorded after the fact. + const [resolved, setResolved] = useState(""); const [parentId, setParentId] = useState(NONE); const [note, setNote] = useState(""); @@ -96,6 +100,9 @@ export function NewEpisodeSheet({ setType(editEpisode?.type ?? "INFECTION"); setLifecycle(editEpisode?.lifecycle ?? "ACUTE"); setOnset(editEpisode ? editEpisode.onsetAt.slice(0, 10) : today); + setResolved( + editEpisode?.resolvedAt ? editEpisode.resolvedAt.slice(0, 10) : "", + ); setParentId(editEpisode?.parentConditionId ?? NONE); setNote(editEpisode?.note ?? ""); } else if (!open && wasOpen) { @@ -104,9 +111,17 @@ export function NewEpisodeSheet({ const showParent = PARENTABLE.includes(lifecycle); + // The end date can never precede the start; the field is min-bounded to the + // onset, and this guard belts-and-braces the same invariant the server holds. + const endBeforeStart = resolved !== "" && resolved < onset; + async function handleSave() { - if (label.trim() === "") return; + if (label.trim() === "" || endBeforeStart) return; const onsetAt = new Date(`${onset}T12:00:00`).toISOString(); + // "" ⇒ ongoing (null clears any stored end). Anchor to local noon so the + // UTC instant lands on the intended calendar day regardless of offset. + const resolvedAt = + resolved === "" ? null : new Date(`${resolved}T12:00:00`).toISOString(); const parentConditionId = showParent && parentId !== NONE ? parentId : null; try { if (isEdit && editEpisode) { @@ -117,6 +132,7 @@ export function NewEpisodeSheet({ type, lifecycle, onsetAt, + resolvedAt, parentConditionId, note: note.trim() === "" ? null : note, }, @@ -126,9 +142,8 @@ export function NewEpisodeSheet({ label: label.trim(), type, lifecycle, - // Anchor to local noon so the UTC instant lands on the intended - // calendar day regardless of the viewer's offset. onsetAt, + resolvedAt, parentConditionId, note: note.trim() === "" ? null : note, }); @@ -154,7 +169,7 @@ export function NewEpisodeSheet({ @@ -240,6 +255,33 @@ export function NewEpisodeSheet({ />
+
+ +
+ + {resolved !== "" ? ( + + ) : null} +
+

+ {t("illness.new.resolvedHelp")} +

+
+