diff --git a/messages/de.json b/messages/de.json index a90fecd6a..23d0ff46c 100644 --- a/messages/de.json +++ b/messages/de.json @@ -4665,7 +4665,9 @@ "focusPlaceholder": "Z. B. behalte meinen Blutdruck im Blick, motiviere mich zu mehr Bewegung …", "completeness": "{answered} von {total} beantwortet", "questionsTitle": "Der Coach hat Rückfragen", - "questionsOpenCoach": "Im Coach beantworten" + "questionsOpenCoach": "Im Coach beantworten", + "recordsLink": "Allergien, Familienanamnese und deine Vorerkrankungen gehören zu deiner Anamnese.", + "recordsLinkCta": "Anamnese öffnen" }, "responseTimeoutHeading": "Antwort-Timeout", "responseTimeoutBody": "Wie lange auf die Antwort der KI gewartet wird, bevor abgebrochen wird. Erhöhe den Wert für ein lokales oder selbst gehostetes Modell, das beim ersten Aufruf Zeit zum Laden braucht.", @@ -7774,6 +7776,15 @@ "updatedToast": "Eintrag aktualisiert", "saveError": "Der Eintrag konnte nicht gespeichert werden." } + }, + "conditions": { + "cardTitle": "Vorerkrankungen", + "cardDescription": "Chronische Erkrankungen und worauf der Coach achten soll — aus deinem persönlichen Kontext.", + "conditionsLabel": "Chronische Erkrankungen", + "focusLabel": "Worauf der Coach achtet", + "empty": "Noch nichts hinterlegt.", + "editCta": "Im persönlichen Kontext bearbeiten", + "loadError": "Vorerkrankungen konnten nicht geladen werden." } }, "environment": { diff --git a/messages/en.json b/messages/en.json index 511493b1c..810ecfc9b 100644 --- a/messages/en.json +++ b/messages/en.json @@ -4665,7 +4665,9 @@ "focusPlaceholder": "E.g. keep an eye on my blood pressure, motivate me to move more…", "completeness": "{answered} of {total} answered", "questionsTitle": "The Coach has follow-up questions", - "questionsOpenCoach": "Answer them in the Coach" + "questionsOpenCoach": "Answer them in the Coach", + "recordsLink": "Allergies, family history and your pre-existing conditions are part of your medical history.", + "recordsLinkCta": "Open medical history" }, "responseTimeoutHeading": "Response timeout", "responseTimeoutBody": "How long to wait for the AI to respond before giving up. Raise it for a local or self-hosted model that needs time to load on the first request.", @@ -7774,6 +7776,15 @@ "updatedToast": "Entry updated", "saveError": "Couldn't save the entry." } + }, + "conditions": { + "cardTitle": "Pre-existing conditions", + "cardDescription": "Chronic conditions and what the Coach watches — from your personal context.", + "conditionsLabel": "Chronic conditions", + "focusLabel": "What the Coach watches", + "empty": "Nothing noted yet.", + "editCta": "Edit in personal context", + "loadError": "Couldn't load your conditions." } }, "environment": { diff --git a/messages/es.json b/messages/es.json index 298329f0c..595bdcb39 100644 --- a/messages/es.json +++ b/messages/es.json @@ -4665,7 +4665,9 @@ "focusPlaceholder": "P. ej. vigila mi tensión arterial, motívame a moverme más…", "completeness": "{answered} de {total} respondidas", "questionsTitle": "El Coach tiene preguntas", - "questionsOpenCoach": "Responderlas en el Coach" + "questionsOpenCoach": "Responderlas en el Coach", + "recordsLink": "Las alergias, los antecedentes familiares y tus enfermedades preexistentes forman parte de tu historia clínica.", + "recordsLinkCta": "Abrir historia clínica" }, "responseTimeoutHeading": "Tiempo de espera de la respuesta", "responseTimeoutBody": "Cuánto esperar a que responda la IA antes de cancelar. Aumenta el valor para un modelo local o autoalojado que necesita tiempo para cargarse en la primera solicitud.", @@ -7774,6 +7776,15 @@ "updatedToast": "Entrada actualizada", "saveError": "No se ha podido guardar la entrada." } + }, + "conditions": { + "cardTitle": "Enfermedades preexistentes", + "cardDescription": "Enfermedades crónicas y aquello que el Coach debe vigilar, desde tu contexto personal.", + "conditionsLabel": "Enfermedades crónicas", + "focusLabel": "Qué vigila el Coach", + "empty": "Aún no hay nada anotado.", + "editCta": "Editar en el contexto personal", + "loadError": "No se pudieron cargar tus enfermedades." } }, "environment": { diff --git a/messages/fr.json b/messages/fr.json index ae54eb4e2..0bd9561de 100644 --- a/messages/fr.json +++ b/messages/fr.json @@ -4665,7 +4665,9 @@ "focusPlaceholder": "P. ex. surveille ma tension, motive-moi à bouger davantage…", "completeness": "{answered} sur {total} renseignés", "questionsTitle": "Le Coach a des questions", - "questionsOpenCoach": "Y répondre dans le Coach" + "questionsOpenCoach": "Y répondre dans le Coach", + "recordsLink": "Les allergies, les antécédents familiaux et vos affections préexistantes font partie de vos antécédents médicaux.", + "recordsLinkCta": "Ouvrir les antécédents médicaux" }, "responseTimeoutHeading": "Délai de réponse", "responseTimeoutBody": "Durée d'attente de la réponse de l'IA avant d'abandonner. Augmentez-la pour un modèle local ou auto-hébergé qui a besoin de temps pour se charger à la première requête.", @@ -7774,6 +7776,15 @@ "updatedToast": "Entrée mise à jour", "saveError": "Impossible d'enregistrer l'entrée." } + }, + "conditions": { + "cardTitle": "Affections préexistantes", + "cardDescription": "Affections chroniques et ce que le Coach doit surveiller, d'après votre contexte personnel.", + "conditionsLabel": "Affections chroniques", + "focusLabel": "Ce que le Coach surveille", + "empty": "Rien de noté pour l'instant.", + "editCta": "Modifier dans le contexte personnel", + "loadError": "Impossible de charger vos antécédents." } }, "environment": { diff --git a/messages/it.json b/messages/it.json index 7d47c79f9..b2717dd0a 100644 --- a/messages/it.json +++ b/messages/it.json @@ -4665,7 +4665,9 @@ "focusPlaceholder": "Es. tieni d'occhio la mia pressione, motivami a muovermi di più…", "completeness": "{answered} su {total} compilati", "questionsTitle": "Il Coach ha alcune domande", - "questionsOpenCoach": "Rispondi nel Coach" + "questionsOpenCoach": "Rispondi nel Coach", + "recordsLink": "Allergie, anamnesi familiare e le tue patologie preesistenti fanno parte della tua anamnesi.", + "recordsLinkCta": "Apri anamnesi" }, "responseTimeoutHeading": "Timeout della risposta", "responseTimeoutBody": "Quanto attendere la risposta dell'IA prima di annullare. Aumentalo per un modello locale o self-hosted che ha bisogno di tempo per caricarsi alla prima richiesta.", @@ -7774,6 +7776,15 @@ "updatedToast": "Voce aggiornata", "saveError": "Impossibile salvare la voce." } + }, + "conditions": { + "cardTitle": "Patologie preesistenti", + "cardDescription": "Patologie croniche e ciò che il Coach deve tenere d'occhio, dal tuo contesto personale.", + "conditionsLabel": "Patologie croniche", + "focusLabel": "Cosa tiene d'occhio il Coach", + "empty": "Ancora nessuna annotazione.", + "editCta": "Modifica nel contesto personale", + "loadError": "Impossibile caricare le tue patologie." } }, "environment": { diff --git a/messages/pl.json b/messages/pl.json index 6e0a5ce53..07760e057 100644 --- a/messages/pl.json +++ b/messages/pl.json @@ -4665,7 +4665,9 @@ "focusPlaceholder": "Np. pilnuj mojego ciśnienia, motywuj mnie do ruchu…", "completeness": "Wypełnione: {answered} z {total}", "questionsTitle": "Coach ma pytania", - "questionsOpenCoach": "Odpowiedz w Coachu" + "questionsOpenCoach": "Odpowiedz w Coachu", + "recordsLink": "Alergie, wywiad rodzinny i Twoje choroby przewlekłe są częścią Twojego wywiadu medycznego.", + "recordsLinkCta": "Otwórz wywiad medyczny" }, "responseTimeoutHeading": "Limit czasu odpowiedzi", "responseTimeoutBody": "Jak długo czekać na odpowiedź AI, zanim nastąpi przerwanie. Zwiększ wartość dla lokalnego lub samodzielnie hostowanego modelu, który potrzebuje czasu na załadowanie przy pierwszym żądaniu.", @@ -7774,6 +7776,15 @@ "updatedToast": "Wpis zaktualizowany", "saveError": "Nie udało się zapisać wpisu." } + }, + "conditions": { + "cardTitle": "Choroby przewlekłe", + "cardDescription": "Choroby przewlekłe i to, na co Coach ma zwracać uwagę — z Twojego kontekstu osobistego.", + "conditionsLabel": "Choroby przewlekłe", + "focusLabel": "Na co zwraca uwagę Coach", + "empty": "Nic jeszcze nie zapisano.", + "editCta": "Edytuj w kontekście osobistym", + "loadError": "Nie udało się wczytać Twoich chorób." } }, "environment": { diff --git a/src/components/insights/coach-panel/coach-hero.tsx b/src/components/insights/coach-panel/coach-hero.tsx index 5cc06c4c9..cd67c0d54 100644 --- a/src/components/insights/coach-panel/coach-hero.tsx +++ b/src/components/insights/coach-panel/coach-hero.tsx @@ -2,7 +2,6 @@ import { Sparkles } from "lucide-react"; -import { MedicalDisclaimer } from "@/components/common/medical-disclaimer"; import { cn } from "@/lib/utils"; import { useTranslations } from "@/lib/i18n/context"; @@ -122,15 +121,6 @@ export function CoachHero({ composer, scopeHint }: CoachHeroProps) { {scopeHint} ) : null} - - {/* v1.25 — the Coach standing self-description: works from your own - data, not a doctor, not medical advice. Sits once on the calm - new-chat surface so it frames the conversation without nagging - every reply. */} - ); diff --git a/src/components/records/conditions-overview.tsx b/src/components/records/conditions-overview.tsx new file mode 100644 index 000000000..2accbd77b --- /dev/null +++ b/src/components/records/conditions-overview.tsx @@ -0,0 +1,98 @@ +"use client"; + +/** + * v1.25.1 — pre-existing / chronic conditions, surfaced in the Anamnese + * (medical-history) home. + * + * The chronic-conditions note and the explicit "what should the Coach watch" + * focus the user entered in Settings → Profile → "About me" are foundational + * medical history, so they are surfaced here next to allergies + family history + * to give one coherent medical-history overview. + * + * This is a READ-ONLY shared view of the existing self-context + * (`GET /api/coach/about-me`) — it reads through the same query-key the "About + * me" panel writes, so the two stay in lockstep. Editing keeps its single home + * in personal context (linked below); nothing is moved, no new storage, no + * schema change. Plain text only — values render as React text children, no + * markdown renderer (XSS posture, see contributor notes). + */ +import Link from "next/link"; +import { useQuery } from "@tanstack/react-query"; +import { Loader2 } from "lucide-react"; + +import { useTranslations } from "@/lib/i18n/context"; +import { queryKeys } from "@/lib/query-keys"; +import { apiGet } from "@/lib/api/api-fetch"; + +interface SelfContext { + conditions: string | null; + coachFocus: string | null; +} + +export function ConditionsOverview() { + const { t } = useTranslations(); + const { data, isLoading, isError } = useQuery({ + queryKey: queryKeys.coachAboutMe(), + queryFn: () => apiGet("/api/coach/about-me"), + }); + + const conditions = (data?.conditions ?? "").trim(); + const coachFocus = (data?.coachFocus ?? "").trim(); + const hasAny = conditions.length > 0 || coachFocus.length > 0; + + return ( +
+ {isLoading && ( + + )} + + {isError && ( +

+ {t("records.conditions.loadError")} +

+ )} + + {!isLoading && !isError && ( + <> + {hasAny ? ( +
+ {conditions.length > 0 && ( +
+
+ {t("records.conditions.conditionsLabel")} +
+
+ {conditions} +
+
+ )} + {coachFocus.length > 0 && ( +
+
+ {t("records.conditions.focusLabel")} +
+
+ {coachFocus} +
+
+ )} +
+ ) : ( +

+ {t("records.conditions.empty")} +

+ )} + +

+ + {t("records.conditions.editCta")} + +

+ + )} +
+ ); +} diff --git a/src/components/settings/about-me-section.tsx b/src/components/settings/about-me-section.tsx index 6683d45a4..12638978d 100644 --- a/src/components/settings/about-me-section.tsx +++ b/src/components/settings/about-me-section.tsx @@ -350,6 +350,19 @@ export function AboutMeSection({

{t("settings.ai.aboutMe.hint")} {t("settings.ai.aboutMe.profileHint")}

+ + {/* v1.25.1 — cross-link to the Anamnese (medical-history) home. The + chronic conditions noted above plus allergies + family history read as + one coherent medical history there. */} +

+ {t("settings.ai.aboutMe.recordsLink")}{" "} + + {t("settings.ai.aboutMe.recordsLinkCta")} + +

); } diff --git a/src/components/settings/anamnesis-section.tsx b/src/components/settings/anamnesis-section.tsx index 546e10384..6b4cfe942 100644 --- a/src/components/settings/anamnesis-section.tsx +++ b/src/components/settings/anamnesis-section.tsx @@ -8,6 +8,13 @@ * card. These are patient-reported reference records — not a time-series * signal and not a clinical diagnosis — surfaced alongside the existing * tracking-domain settings sections (Labs / Illness / Vorsorge). + * + * v1.25.1 — the section also surfaces the pre-existing / chronic conditions the + * Coach watches (the self-context entered under Profile → "About me") as a + * read-only shared view, so conditions + allergies + family history read as one + * coherent medical history. The conditions card is coach-gated (the data only + * feeds the Coach) and links back to its single editing home in personal + * context. */ import { @@ -18,14 +25,31 @@ import { CardTitle, } from "@/components/ui/card"; import { useTranslations } from "@/lib/i18n/context"; +import { useModuleEnabled } from "@/hooks/use-module-enabled"; import { AllergyManager } from "@/components/records/allergy-manager"; +import { ConditionsOverview } from "@/components/records/conditions-overview"; import { FamilyHistoryManager } from "@/components/records/family-history-manager"; export function AnamnesisSection() { const { t } = useTranslations(); + const coachEnabled = useModuleEnabled("coach"); return (
+ {coachEnabled && ( + + + {t("records.conditions.cardTitle")} + + {t("records.conditions.cardDescription")} + + + + + + + )} + {t("records.allergies.cardTitle")} diff --git a/src/components/settings/security-activity-card.tsx b/src/components/settings/security-activity-card.tsx index 7f7426984..bc3450dbb 100644 --- a/src/components/settings/security-activity-card.tsx +++ b/src/components/settings/security-activity-card.tsx @@ -8,14 +8,15 @@ * shared `GET /api/auth/me/security-activity` endpoint. Each row shows a * readable action label, the resolved location, the masked IP, and the time. */ +import { useId, useState } from "react"; import { useQuery } from "@tanstack/react-query"; -import { Loader2, ShieldCheck } from "lucide-react"; +import { ChevronDown, Loader2, ShieldCheck } from "lucide-react"; import { SettingsCard } from "@/components/settings/settings-card"; -import { SettingsCardHeader } from "@/components/settings/_card-header"; import { useTranslations } from "@/lib/i18n/context"; import { queryKeys } from "@/lib/query-keys"; import { apiGet } from "@/lib/api/api-fetch"; +import { cn } from "@/lib/utils"; interface ActivityRow { action: string; @@ -53,6 +54,10 @@ export function SecurityActivityCard({ isAuthenticated: boolean; }) { const { t } = useTranslations(); + // Collapsed by default — the feed opens only on demand, keeping the security + // surface skimmable. UI-only state; nothing is persisted across reloads. + const [open, setOpen] = useState(false); + const regionId = useId(); const { data, isLoading, isError } = useQuery({ queryKey: queryKeys.securityActivity(), @@ -65,12 +70,32 @@ export function SecurityActivityCard({ return ( - -
+ +