diff --git a/README.md b/README.md index f0f168074..1a6994b5f 100644 --- a/README.md +++ b/README.md @@ -28,14 +28,19 @@ HealthLog is a self-hosted health tracking app (PWA + native iOS client) that runs from a single `docker compose up` on a NAS, homelab, or small VPS. It brings weight, blood pressure, pulse, blood glucose, body composition, sleep, mood, menstrual cycle, and medications onto one timeline — synced from the devices you already own — and keeps everything encrypted at rest on hardware you control. No vendor cloud, no subscription, no telemetry. +HealthLog is a personal health record-keeping and wellness tool. It is not a medical device and does not diagnose, treat, cure, or prevent any disease — it helps you log, visualise, and export your own data, and leaves clinical judgement to a clinician. + Try the [live demo](https://demo.healthlog.dev) to see a working install — sign in with `demo` / `demo123demo123`. ## Highlights -- **Every vital on one timeline.** Weight, blood pressure, pulse, glucose, body composition, sleep, SpO₂, mood, and cycle tracking — charted with trends, moving averages, and clinical reference ranges (ESH 2023, ADA 2024), overridable with the targets your clinician set. +- **Every vital on one timeline.** Weight, blood pressure, pulse, glucose, body composition, sleep, SpO₂, respiratory rate, mood, and cycle tracking — charted with trends, moving averages, and clinical reference ranges (ESH 2023, ADA 2024), overridable with the targets your clinician set. +- **Clinical signals, opt-in and additive.** Validated mental-wellbeing self-checks (PHQ-9, GAD-7) that sit beside mood tracking with calm crisis-resource handling, grip strength, a 0–10 pain score, waist and body measurements, and a longevity lab panel — each added through one signal registry, so a new signal wires into charts, correlations, the coach, the FHIR map, and the API at once. +- **Records and the documents you're handed.** Structured allergies and family history (encrypted), lab values you key in or scan, and inbound documents — discharge letters and reports — read to extract the facts they state (values, codes, dates) for you to review and confirm. HealthLog transcribes what a document says; it never interprets or diagnoses from it. (Immunization records are deliberately out of scope.) +- **Context that explains the numbers.** An opt-in environmental module folds weather, daylight, and temperature in as influence signals (winter darkness against mood, heat against blood pressure) using a home location you set, so a pattern has somewhere to point. - **Your devices, one server.** Withings, WHOOP, Oura and Polar sync over OAuth2, Fitbit connects experimentally over the Fitbit Web API, an Apple Health `export.zip` folds your full history in, and the native iOS app streams HealthKit live. A per-metric source priority decides which reading is canonical when wearables overlap. - **Medication tracking that tells the truth.** Flexible schedules (weekly injections, weekday-only, intervals, PRN, cyclic), a configurable intake window per dose, and a traceable dose history where every slot reads taken, late, skipped, or missed — the same ledger the compliance rate is computed from, so the percentage can never disagree with the timeline. -- **AI insights you own.** Daily briefing, health scores, correlations, and a conversational coach grounded in your own measurements, with proactive check-ins. Bring your own OpenAI or Anthropic key, or point at a local endpoint (Ollama, LM Studio, vLLM) so nothing leaves your network. +- **AI insights you own.** Daily briefing, health scores, correlations, and a conversational coach grounded in your own measurements, with calm proactive check-ins it suppresses when there's nothing genuine to say. The coach describes your data, never diagnoses. Bring your own OpenAI or Anthropic key, or point at a local endpoint (Ollama, LM Studio, vLLM) so nothing leaves your network. - **Clinician-ready output.** A doctor-report PDF generated client-side, a read-only HL7 FHIR R4 API, and scoped, time-limited share links you revoke after the visit. - **Private by construction.** AES-256-GCM encryption at rest with zero-downtime key rotation, passkey login, server-side sessions, strict CSP — and no third-party tracking anywhere. - **Built to be lived in.** Installable PWA with offline support, medication reminders over APNs, Telegram, ntfy, and Web Push, a sub-second dashboard on years of imported history, English and German end to end. diff --git a/docs/api/mcp-capabilities.md b/docs/api/mcp-capabilities.md index 66874523d..e467969bf 100644 --- a/docs/api/mcp-capabilities.md +++ b/docs/api/mcp-capabilities.md @@ -28,7 +28,10 @@ them up front): 4. **Data and context only.** The server ships facts; it never returns a diagnosis, clinical verdict, risk score, or treatment change. The assistant narrates the facts and leaves clinical judgement to the - user's clinician. + user's clinician. This mirrors HealthLog's project-wide self-description + standard: the app is a personal health record-keeping and wellness tool, + not a medical device, and nothing it surfaces — here or in the UI — + diagnoses or treats. Discover before fetching: call `list_metrics` (or read the `healthlog://measurements/inventory` resource) first to see what exists, @@ -106,7 +109,10 @@ surfaces a signal only when the user has recorded it; `fetch metric:` hydrates it). Each carries its unit and population reference band from the single signal registry; absence is the usual `{ present: false }`. They sit off the Coach data inventory by design, so `list_metrics` / -`get_metric_series` do not enumerate them — use the reads above. +`get_metric_series` do not enumerate them — use the reads above. A new +signal is declared once in the in-app signal registry and shows up across +the metric tools, correlations, and the FHIR map together, so the MCP +surface never advertises a signal the record can't return. The **longevity lab panel** (ApoB, Lp(a), hs-CRP, HbA1c, fasting glucose, fasting insulin, eGFR, GGT, ferritin, omega-3 index) is reached like any @@ -118,10 +124,18 @@ curated biomarker catalogue. **Off the MCP surface by construction.** The PHQ-9 / GAD-7 mental-health screeners (item content and totals) are excluded from AI / MCP, and the environmental-context signals (`ENV_*`: temperature, sunshine, daylight, -precipitation, barometric pressure) are off MCP in v1. None of them -resolve through any read tool — they are not on the MCP exposure +precipitation, barometric pressure) are off MCP in v1. The structured +records (allergies, family history) and the inbound-document store are +deliberately not exposed either: the document pipeline extracts facts for +the user to confirm in-app rather than streaming them to a model. None of +them resolve through any read tool — they are not on the MCP exposure allowlist. +This mirrors HealthLog's project-wide self-description standard: the MCP +surface ships data and context, never a diagnosis, clinical verdict, or +treatment change — the assistant narrates the facts and leaves clinical +judgement to the user's clinician. + ## Resources Read-only context the assistant can attach alongside tool results. Like diff --git a/docs/integrations/ai-providers.md b/docs/integrations/ai-providers.md index 1b1600b7e..2cdb85c7b 100644 --- a/docs/integrations/ai-providers.md +++ b/docs/integrations/ai-providers.md @@ -198,6 +198,30 @@ The Coach drawer surfaces a "Local" badge when generation resolves to the local provider, so users who pinned a local endpoint can see at a glance that their context stayed on the host. +## What the Coach is — and isn't + +The Coach is an informational assistant that works from your own +logged data. It is not a doctor, what it says is not medical advice, +and it does not diagnose, prescribe, or change treatment — those +boundaries are written into its system prompt as safety contracts, not +left to chance. It describes patterns observationally ("your resting +heart rate has been rising over 14 days"), never as a verdict about a +named condition, and it defers diagnosis, dose, and drug-interaction +questions to a healthcare professional. This is HealthLog's +project-wide self-description standard: a personal health +record-keeping and wellness tool, not a medical device. The same +non-diagnostic framing governs the daily briefing, the insights, and +every surface that turns your data into words. + +The Coach can also reach out proactively — a calm, once-a-day morning +check-in when there is something genuine to surface (a recovery trend +easing off, blood pressure running a touch high). The nudge is warm +and localized, references the focus by category rather than quoting +your own words back, and stays silent when there is nothing real to +say or when you have engaged the Coach recently. It is an invitation, +never a streak-guilt prompt, and it inherits the same non-diagnostic +boundaries as the chat. + ## Caching contract Two caches sit in front of every provider call so generation budgets diff --git a/docs/self-hosting/getting-started.md b/docs/self-hosting/getting-started.md index 03135bd6f..6e59c8a7f 100644 --- a/docs/self-hosting/getting-started.md +++ b/docs/self-hosting/getting-started.md @@ -16,6 +16,10 @@ already installed, longer if a TLS-fronted public hostname is in scope hold a 1.5 GB upload in `/tmp` while it parses. - **Outbound HTTPS** to `ghcr.io` (image pulls) plus whichever integration endpoints you plan to enable (Withings, OpenAI, etc.). + The opt-in environmental-context module additionally reaches + `api.open-meteo.com` for weather and daylight; it stays off until you + enable it and set a home location, and every outbound call goes + through the SSRF-guarded egress wrapper. - **Optional but recommended:** a reverse proxy that terminates TLS and forwards to `http://localhost:3000` (Caddy, Traefik, Nginx Proxy Manager, Coolify, or bare Nginx — see `reverse-proxy.md`). diff --git a/messages/de.json b/messages/de.json index d971d07b8..674c2f869 100644 --- a/messages/de.json +++ b/messages/de.json @@ -7792,5 +7792,14 @@ "saved": "Angabe aktualisiert.", "discarded": "Dokument verworfen." } + }, + "selfDescription": { + "intendedPurpose": "HealthLog ist ein persönliches Werkzeug zur Gesundheitsdokumentation und zum Wohlbefinden. Es hilft dir, deine eigenen Gesundheitsdaten zu erfassen, zu visualisieren und zu exportieren. Es diagnostiziert, behandelt, heilt oder verhindert keine Krankheit und ist kein Medizinprodukt.", + "coach": "Der Coach arbeitet mit deinen eigenen erfassten Daten. Er ist kein Arzt und gibt keine medizinische Beratung — für Diagnose oder Behandlung wende dich an medizinisches Fachpersonal.", + "insights": "Das sind beschreibende Muster aus deinen eigenen erfassten Daten, keine klinische Beurteilung und keine Diagnose.", + "labs": "Das sind deine eigenen Aufzeichnungen, keine medizinische Diagnose. Referenzbereiche unterscheiden sich je nach Labor und Person — nur deine Ärztin oder dein Arzt kann sie für dich einordnen.", + "medications": "Das erfasst die von dir dokumentierten Medikamente und deine eigene Einnahmetreue. Es beurteilt weder Dosierung noch Wechselwirkungen und ist keine medizinische Beratung — kläre das mit Apotheke oder Arztpraxis.", + "emergency": "Falls das ein Notfall sein könnte, wende dich jetzt an deine örtliche Notrufnummer oder an eine ärztliche Stelle. HealthLog kann im Notfall nicht helfen.", + "dataPosture": "HealthLog wird selbst gehostet: Deine Gesundheitsdaten bleiben auf deiner eigenen Instanz und sind im Ruhezustand verschlüsselt. KI-Funktionen nutzen einen von dir mitgebrachten Anbieter-Schlüssel oder ein lokales Modell — nichts verlässt deine Instanz über den von dir konfigurierten Anbieter hinaus, und es gibt keine Drittanbieter-Analyse deiner Gesundheitsdaten." } } diff --git a/messages/en.json b/messages/en.json index 5c88e09b7..bcdee2c5e 100644 --- a/messages/en.json +++ b/messages/en.json @@ -7792,5 +7792,14 @@ "saved": "Fact updated.", "discarded": "Document discarded." } + }, + "selfDescription": { + "intendedPurpose": "HealthLog is a personal health record-keeping and wellness tool. It helps you log, visualise, and export your own health data. It does not diagnose, treat, cure, or prevent any disease, and it is not a medical device.", + "coach": "The Coach works from your own logged data. It isn't a doctor and doesn't give medical advice — for diagnosis or treatment, talk to a healthcare professional.", + "insights": "These are descriptive patterns computed from your own logged data, not a clinical assessment or diagnosis.", + "labs": "These are your own records, not a medical diagnosis. Reference ranges vary by lab and person — only your clinician can interpret them for you.", + "medications": "This tracks the medications you log and your own adherence. It doesn't assess dosing or interactions and isn't medical advice — confirm those with a pharmacist or doctor.", + "emergency": "If this might be an emergency, contact your local emergency number or a clinician now. HealthLog can't help in an emergency.", + "dataPosture": "HealthLog is self-hosted: your health data stays on your own instance, encrypted at rest. AI features use a provider key you bring or a local model, so nothing leaves your instance beyond the provider you configure, and there is no third-party analytics on your health data." } } diff --git a/messages/es.json b/messages/es.json index 82610e32b..663e4b38c 100644 --- a/messages/es.json +++ b/messages/es.json @@ -7792,5 +7792,14 @@ "saved": "Dato actualizado.", "discarded": "Documento descartado." } + }, + "selfDescription": { + "intendedPurpose": "HealthLog es una herramienta personal de registro de salud y bienestar. Te ayuda a registrar, visualizar y exportar tus propios datos de salud. No diagnostica, trata, cura ni previene ninguna enfermedad, y no es un producto sanitario.", + "coach": "El Coach trabaja con tus propios datos registrados. No es un médico y no da consejo médico: para un diagnóstico o tratamiento, consulta a un profesional sanitario.", + "insights": "Son patrones descriptivos calculados a partir de tus propios datos registrados, no una evaluación clínica ni un diagnóstico.", + "labs": "Son tus propios registros, no un diagnóstico médico. Los rangos de referencia varían según el laboratorio y la persona: solo tu médico puede interpretarlos por ti.", + "medications": "Esto registra los medicamentos que anotas y tu propia adherencia. No evalúa dosis ni interacciones y no es consejo médico: confírmalo con tu farmacéutico o médico.", + "emergency": "Si esto pudiera ser una emergencia, llama ahora a tu número de emergencias local o a un profesional sanitario. HealthLog no puede ayudar en una emergencia.", + "dataPosture": "HealthLog es autoalojado: tus datos de salud permanecen en tu propia instancia, cifrados en reposo. Las funciones de IA usan una clave de proveedor que tú aportas o un modelo local, así que nada sale de tu instancia más allá del proveedor que configures, y no hay analítica de terceros sobre tus datos de salud." } } diff --git a/messages/fr.json b/messages/fr.json index a7a2052d7..dd098d408 100644 --- a/messages/fr.json +++ b/messages/fr.json @@ -7792,5 +7792,14 @@ "saved": "Donnée mise à jour.", "discarded": "Document écarté." } + }, + "selfDescription": { + "intendedPurpose": "HealthLog est un outil personnel de suivi de santé et de bien-être. Il vous aide à enregistrer, visualiser et exporter vos propres données de santé. Il ne diagnostique, ne traite, ne guérit ni ne prévient aucune maladie, et n'est pas un dispositif médical.", + "coach": "Le Coach s'appuie sur vos propres données enregistrées. Ce n'est pas un médecin et il ne donne pas de conseil médical : pour un diagnostic ou un traitement, consultez un professionnel de santé.", + "insights": "Ce sont des tendances descriptives calculées à partir de vos propres données enregistrées, pas une évaluation clinique ni un diagnostic.", + "labs": "Ce sont vos propres relevés, pas un diagnostic médical. Les valeurs de référence varient selon le laboratoire et la personne : seul votre médecin peut les interpréter pour vous.", + "medications": "Ceci suit les médicaments que vous enregistrez et votre propre observance. Cela n'évalue ni les doses ni les interactions et n'est pas un conseil médical : confirmez-les avec votre pharmacien ou votre médecin.", + "emergency": "Si cela pourrait être une urgence, contactez maintenant votre numéro d'urgence local ou un professionnel de santé. HealthLog ne peut pas aider en cas d'urgence.", + "dataPosture": "HealthLog est auto-hébergé : vos données de santé restent sur votre propre instance, chiffrées au repos. Les fonctions d'IA utilisent une clé de fournisseur que vous apportez ou un modèle local, donc rien ne quitte votre instance au-delà du fournisseur que vous configurez, et il n'y a aucune analyse tierce de vos données de santé." } } diff --git a/messages/it.json b/messages/it.json index 94885be49..f85dcb938 100644 --- a/messages/it.json +++ b/messages/it.json @@ -7792,5 +7792,14 @@ "saved": "Dato aggiornato.", "discarded": "Documento scartato." } + }, + "selfDescription": { + "intendedPurpose": "HealthLog è uno strumento personale per la registrazione della salute e il benessere. Ti aiuta a registrare, visualizzare ed esportare i tuoi dati di salute. Non diagnostica, cura, guarisce né previene alcuna malattia e non è un dispositivo medico.", + "coach": "Il Coach lavora sui tuoi dati registrati. Non è un medico e non fornisce consigli medici: per una diagnosi o una terapia rivolgiti a un professionista sanitario.", + "insights": "Sono andamenti descrittivi calcolati dai tuoi dati registrati, non una valutazione clinica né una diagnosi.", + "labs": "Sono i tuoi referti, non una diagnosi medica. Gli intervalli di riferimento variano da laboratorio a persona: solo il tuo medico può interpretarli per te.", + "medications": "Questo registra i farmaci che annoti e la tua aderenza. Non valuta dosaggi o interazioni e non è un consiglio medico: confermali con il farmacista o il medico.", + "emergency": "Se potrebbe trattarsi di un'emergenza, contatta subito il numero di emergenza locale o un medico. HealthLog non può aiutare in caso di emergenza.", + "dataPosture": "HealthLog è self-hosted: i tuoi dati di salute restano sulla tua istanza, cifrati a riposo. Le funzioni di IA usano una chiave del provider che fornisci tu o un modello locale, quindi nulla lascia la tua istanza oltre il provider che configuri, e non c'è alcuna analisi di terze parti sui tuoi dati di salute." } } diff --git a/messages/pl.json b/messages/pl.json index 70a7fea64..c56dbf0df 100644 --- a/messages/pl.json +++ b/messages/pl.json @@ -7792,5 +7792,14 @@ "saved": "Dane zaktualizowane.", "discarded": "Dokument odrzucony." } + }, + "selfDescription": { + "intendedPurpose": "HealthLog to osobiste narzędzie do rejestrowania zdrowia i dbania o dobre samopoczucie. Pomaga zapisywać, wizualizować i eksportować własne dane zdrowotne. Nie diagnozuje, nie leczy ani nie zapobiega żadnej chorobie i nie jest wyrobem medycznym.", + "coach": "Coach korzysta z Twoich własnych zapisanych danych. Nie jest lekarzem i nie udziela porad medycznych — po diagnozę lub leczenie zwróć się do pracownika ochrony zdrowia.", + "insights": "To opisowe wzorce wyliczone z Twoich własnych zapisanych danych, a nie ocena kliniczna ani diagnoza.", + "labs": "To Twoje własne zapisy, a nie diagnoza medyczna. Zakresy referencyjne różnią się w zależności od laboratorium i osoby — tylko Twój lekarz może je dla Ciebie zinterpretować.", + "medications": "To rejestruje leki, które zapisujesz, oraz Twoje stosowanie się do zaleceń. Nie ocenia dawkowania ani interakcji i nie jest poradą medyczną — potwierdź to u farmaceuty lub lekarza.", + "emergency": "Jeśli to może być nagły przypadek, zadzwoń teraz pod lokalny numer alarmowy lub skontaktuj się z lekarzem. HealthLog nie pomoże w nagłym przypadku.", + "dataPosture": "HealthLog jest hostowany samodzielnie: Twoje dane zdrowotne pozostają na Twojej instancji, zaszyfrowane w spoczynku. Funkcje AI korzystają z dostarczonego przez Ciebie klucza dostawcy lub modelu lokalnego, więc nic nie opuszcza Twojej instancji poza skonfigurowanym dostawcą i nie ma analityki firm trzecich na Twoich danych zdrowotnych." } } diff --git a/src/app/labs/page.tsx b/src/app/labs/page.tsx index b10436be5..359f8d1b2 100644 --- a/src/app/labs/page.tsx +++ b/src/app/labs/page.tsx @@ -6,6 +6,7 @@ import { useRouter } from "next/navigation"; import { useQueryClient } from "@tanstack/react-query"; import { Loader2, Pencil, Plus, ScanLine, Wrench } from "lucide-react"; +import { MedicalDisclaimer } from "@/components/common/medical-disclaimer"; import { LabForm } from "@/components/labs/lab-form"; import { LabList } from "@/components/labs/lab-list"; import { OcrReviewDialog } from "@/components/labs/ocr-review-dialog"; @@ -173,6 +174,11 @@ export default function LabsPage() { ) : null} setDialogOpen(true)} /> + + {/* v1.25 — calm, context-specific framing for lab values: your own + records, ranges vary by lab and person, only a clinician interprets + them. One footer line, not a per-row banner. */} + ); } diff --git a/src/app/medications/page.tsx b/src/app/medications/page.tsx index a65d0182e..6b48c6dbc 100644 --- a/src/app/medications/page.tsx +++ b/src/app/medications/page.tsx @@ -7,6 +7,7 @@ import { useRouter, useSearchParams } from "next/navigation"; import { useAuth } from "@/hooks/use-auth"; import { useTranslations } from "@/lib/i18n/context"; import { queryKeys } from "@/lib/query-keys"; +import { MedicalDisclaimer } from "@/components/common/medical-disclaimer"; import { MedicationWizardDialog } from "@/components/medications/wizard/medication-wizard-dialog"; import { MedicationCard } from "@/components/medications/medication-card"; import { Glp1MedicationCard } from "@/components/medications/glp1-medication-card"; @@ -524,6 +525,12 @@ export default function MedicationsPage() { )} + {/* v1.25 — calm framing for the medications surface: this tracks what + you log and your own adherence, it doesn't assess dosing or + interactions and isn't medical advice. One footer line, neutral — + the med cards themselves stay un-tinted. */} + + {/* v1.5.5 — the per-card kebab triggers used to open inline IntakeImportDialog / ApiEndpointDialog mounts here, but both state slots were only ever set from inside the dialogs diff --git a/src/components/common/medical-disclaimer.tsx b/src/components/common/medical-disclaimer.tsx new file mode 100644 index 000000000..ba27663cf --- /dev/null +++ b/src/components/common/medical-disclaimer.tsx @@ -0,0 +1,75 @@ +"use client"; + +import { cn } from "@/lib/utils"; +import { useTranslations } from "@/lib/i18n/context"; + +/** + * v1.25 — the one place the app states what it is and is not. + * + * HealthLog's self-description standard is layered, not a wall of warnings: + * a single intended-purpose statement plus calm, context-specific notes that + * sit once on the surface they belong to. Every string is plain React text — + * no markdown renderer, in line with the project's XSS posture (Coach, + * briefing and insights all render as text children). + * + * The copy lives under the `selfDescription` i18n namespace so every locale + * carries the same set; pick the note that fits the surface: + * + * - `intendedPurpose` — the canonical "what this is / is not, not a medical + * device" line. Reuse verbatim; do not paraphrase per surface. + * - `coach` — the Coach standing line (works from your data, not a doctor). + * - `insights` — descriptive-patterns framing for insights + the briefing. + * - `labs` — your own records, ranges vary, only a clinician interprets. + * - `medications` — tracks what you log, not dosing/interaction advice. + * - `emergency` — the red-flag interstitial note (contact emergency services). + * - `dataPosture` — self-hosted, data stays on your instance, BYOK / local AI. + * + * The acknowledged onboarding disclaimer (`onboarding.disclaimer.*`) and the + * legal medical-device boundary on the public privacy page remain the standing + * floor; these notes are the gentle, in-context layer above it. + */ +export type DisclaimerVariant = + | "intendedPurpose" + | "coach" + | "insights" + | "labs" + | "medications" + | "emergency" + | "dataPosture"; + +const VARIANT_KEY: Record = { + intendedPurpose: "selfDescription.intendedPurpose", + coach: "selfDescription.coach", + insights: "selfDescription.insights", + labs: "selfDescription.labs", + medications: "selfDescription.medications", + emergency: "selfDescription.emergency", + dataPosture: "selfDescription.dataPosture", +}; + +export interface MedicalDisclaimerProps { + variant: DisclaimerVariant; + className?: string; +} + +/** + * A calm, muted, plain-text disclaimer line. Defaults to a small, + * unobtrusive footnote treatment; pass `className` to tune spacing for the + * surface. Keep it to one instance per surface — the standard is layered, + * not repeated. + */ +export function MedicalDisclaimer({ + variant, + className, +}: MedicalDisclaimerProps) { + const { t } = useTranslations(); + return ( +

+ {t(VARIANT_KEY[variant])} +

+ ); +} diff --git a/src/components/insights/coach-panel/coach-hero.tsx b/src/components/insights/coach-panel/coach-hero.tsx index cd67c0d54..5cc06c4c9 100644 --- a/src/components/insights/coach-panel/coach-hero.tsx +++ b/src/components/insights/coach-panel/coach-hero.tsx @@ -2,6 +2,7 @@ import { Sparkles } from "lucide-react"; +import { MedicalDisclaimer } from "@/components/common/medical-disclaimer"; import { cn } from "@/lib/utils"; import { useTranslations } from "@/lib/i18n/context"; @@ -121,6 +122,15 @@ 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/insights/insights-layout-shell.tsx b/src/components/insights/insights-layout-shell.tsx index f8a76fc3b..e7ee0c0ce 100644 --- a/src/components/insights/insights-layout-shell.tsx +++ b/src/components/insights/insights-layout-shell.tsx @@ -8,6 +8,7 @@ import { useMounted } from "@/hooks/use-mounted"; import { useFeatureFlags } from "@/hooks/use-feature-flags"; import { useWorkouts } from "@/hooks/use-workouts"; import { InsightsTabStrip } from "@/components/insights/insights-tab-strip"; +import { MedicalDisclaimer } from "@/components/common/medical-disclaimer"; import { nextAdvisorPollInterval, useInsightsAdvisorQuery, @@ -159,11 +160,14 @@ export function InsightsLayoutShell({ children }: { children: ReactNode }) { modules={user?.modules} /> {children} - {/* v1.18.6 (DISC-01) — the page-level generic medical disclaimer is - removed. The "HealthLog is a private tracking tool, not a clinical - assessment or diagnosis" acknowledgment now lives once at the start - of onboarding, and the legal text stays reachable on the public - privacy page. */} + {/* v1.18.6 (DISC-01) — the page-level generic medical disclaimer was + removed; the standing acknowledgment lives once at the start of + onboarding and the legal text on the public privacy page. + v1.25 — one calm, context-specific framing line for the whole + Insights surface (the briefing, trends and correlations included): + these are descriptive patterns from the user's own data, not a + clinical assessment. Layered, not a wall of warnings. */} + ); } diff --git a/src/components/settings/ai/ai-insights-card.tsx b/src/components/settings/ai/ai-insights-card.tsx index 2768c96e6..e22a1d6b2 100644 --- a/src/components/settings/ai/ai-insights-card.tsx +++ b/src/components/settings/ai/ai-insights-card.tsx @@ -19,6 +19,7 @@ import { useQuery, useQueryClient } from "@tanstack/react-query"; import { Sparkles } from "lucide-react"; import { Badge } from "@/components/ui/badge"; +import { MedicalDisclaimer } from "@/components/common/medical-disclaimer"; import { Label } from "@/components/ui/label"; import { NativeSelect } from "@/components/ui/native-select"; import { SettingsCard } from "@/components/settings/settings-card"; @@ -201,6 +202,11 @@ export function AiInsightsCard({ }) } /> + + {/* v1.25 — the data-posture statement lives where the provider key is + configured: self-hosted, data stays on this instance, BYOK / local + model, no third-party analytics. One calm line, not a banner. */} + ); diff --git a/src/lib/ai/coach/system-prompt.ts b/src/lib/ai/coach/system-prompt.ts index 27ec7d92d..0d2c31c2a 100644 --- a/src/lib/ai/coach/system-prompt.ts +++ b/src/lib/ai/coach/system-prompt.ts @@ -285,6 +285,19 @@ GROUND RULES persuasion for its own sake. If a nudge would only serve to push, drop it and reflect instead. You inform and support a decision that stays theirs; you never manipulate it. +21. Describe HealthLog honestly when asked what you are, what this app is, + or where the user's data goes. HealthLog is a self-hosted personal + health record-keeping and wellness tool — it helps the user log, + visualise and export their OWN data. It is NOT a medical device and does + NOT diagnose, treat, cure or prevent any disease; you are an + informational assistant, not a doctor, and what you offer is not medical + advice. On data: the user's health data stays on their own instance, + encrypted at rest; AI runs on a provider key they bring or a local + model, so nothing leaves their instance beyond the provider they + configured, and there is no third-party analytics on their health data. + State this plainly and calmly when relevant; never oversell it as + confidential like a clinician's, and keep deferring diagnosis / + treatment / dose questions to a healthcare professional. DAY-LEVEL READINGS — USE THE TIMELINE @@ -844,6 +857,21 @@ GRUNDREGELN selbst willen. Würde ein Anstoß nur dem Drängen dienen, lass ihn weg und spiegele stattdessen. Du informierst und unterstützt eine Entscheidung, die seine bleibt; du manipulierst sie nie. +21. Beschreibe HealthLog ehrlich, wenn der Nutzer fragt, was du bist, was + diese App ist oder wohin seine Daten gehen. HealthLog ist ein selbst + gehostetes, persönliches Werkzeug zur Gesundheitsdokumentation und zum + Wohlbefinden — es hilft dem Nutzer, seine EIGENEN Daten zu erfassen, zu + visualisieren und zu exportieren. Es ist KEIN Medizinprodukt und + diagnostiziert, behandelt, heilt oder verhindert KEINE Krankheit; du bist + ein informativer Begleiter, kein Arzt, und was du anbietest, ist keine + medizinische Beratung. Zu den Daten: Die Gesundheitsdaten des Nutzers + bleiben auf seiner eigenen Instanz, im Ruhezustand verschlüsselt; die KI + läuft über einen vom Nutzer mitgebrachten Anbieter-Schlüssel oder ein + lokales Modell, sodass nichts seine Instanz über den konfigurierten + Anbieter hinaus verlässt, und es gibt keine Drittanbieter-Analyse seiner + Gesundheitsdaten. Sag das klar und ruhig, wenn es passt; stell es nie als + vertraulich wie bei einer Ärztin dar und verweise Diagnose-, Behandlungs- + und Dosisfragen weiterhin an medizinisches Fachpersonal. TAGES-LEVEL-MESSWERTE — NUTZE DIE TIMELINE