Derive the privacy page's stored-data list from the module registry

"What's stored" was six hand-kept lines that never named documents,
vaccinations, the cycle or workouts. It now names every module that
keeps records, read off the registry so a new module joins without an
edit, beside fixed lines for measurements, the profile, integration
tokens and account security, in foreground text since it is the content
of the card.
This commit is contained in:
Marc-André Bombeck committed 2026-10-08 20:46:51 +02:00
1 parent c4272343e3
commit f648168266
9 files changed
+116 -40

No files matched your search

+4 -5
View File
@@ -6987,12 +6987,11 @@
},
"stored": {
"title": "Was gespeichert wird",
"measurements": "Messwerte (Gewicht, Blutdruck, Puls, Glukose, Schlaf, Körperzusammensetzung)",
"medications": "Medikamente und Einnahmeverlauf",
"moodLabs": "Stimmungseinträge und Laborwerte",
"coach": "Coach-Unterhaltungen",
"measurements": "Messwerte: Gewicht, Blutdruck, Puls und jeder weitere Wert, den du oder ein Gerät erfasst",
"integrations": "Integrations-Tokens (verschlüsselt)",
"security": "Konto- und Sicherheitsaktivität (Sitzungen, Anmeldeprotokoll)"
"security": "Konto- und Sicherheitsaktivität (Sitzungen, Anmeldeprotokoll)",
"profile": "Dein Profil, deine Anamnese und die Angaben für die Gesundheitsakte",
"modules": "Einträge der Module: {list}"
},
"retention": {
"title": "Aufbewahrung",
+4 -5
View File
@@ -6987,12 +6987,11 @@
},
"stored": {
"title": "What's stored",
"measurements": "Measurements (weight, blood pressure, pulse, glucose, sleep, body composition)",
"medications": "Medications and intake history",
"moodLabs": "Mood entries and lab results",
"coach": "Coach conversations",
"measurements": "Measurements: weight, blood pressure, pulse and every other value you or a device record",
"integrations": "Integration tokens (encrypted)",
"security": "Account and security activity (sessions, sign-in log)"
"security": "Account and security activity (sessions, sign-in log)",
"profile": "Your profile, medical history and health-record details",
"modules": "Records of each module: {list}"
},
"retention": {
"title": "Retention",
+4 -5
View File
@@ -6987,12 +6987,11 @@
},
"stored": {
"title": "Qué se almacena",
"measurements": "Mediciones (peso, presión arterial, pulso, glucosa, sueño, composición corporal)",
"medications": "Medicamentos e historial de tomas",
"moodLabs": "Entradas de estado de ánimo y resultados de laboratorio",
"coach": "Conversaciones con el asistente",
"measurements": "Mediciones: peso, presión arterial, pulso y cualquier otro valor que registres tú o un dispositivo",
"integrations": "Tokens de integración (cifrados)",
"security": "Actividad de la cuenta y de seguridad (sesiones, registro de inicio de sesión)"
"security": "Actividad de la cuenta y de seguridad (sesiones, registro de inicio de sesión)",
"profile": "Tu perfil, tus antecedentes médicos y los datos del historial de salud",
"modules": "Registros de cada módulo: {list}"
},
"retention": {
"title": "Retención",
+4 -5
View File
@@ -6987,12 +6987,11 @@
},
"stored": {
"title": "Ce qui est stocké",
"measurements": "Mesures (poids, tension, pouls, glycémie, sommeil, composition corporelle)",
"medications": "Médicaments et historique des prises",
"moodLabs": "Entrées d'humeur et résultats de laboratoire",
"coach": "Conversations avec le coach",
"measurements": "Mesures : poids, tension, pouls et toute autre valeur enregistrée par vous ou un appareil",
"integrations": "Jetons d'intégration (chiffrés)",
"security": "Activité du compte et de sécurité (sessions, journal de connexion)"
"security": "Activité du compte et de sécurité (sessions, journal de connexion)",
"profile": "Votre profil, vos antécédents médicaux et les informations du dossier de santé",
"modules": "Données de chaque module : {list}"
},
"retention": {
"title": "Conservation",
+4 -5
View File
@@ -6987,12 +6987,11 @@
},
"stored": {
"title": "Cosa viene memorizzato",
"measurements": "Misurazioni (peso, pressione, polso, glicemia, sonno, composizione corporea)",
"medications": "Farmaci e cronologia delle assunzioni",
"moodLabs": "Voci dell'umore e referti di laboratorio",
"coach": "Conversazioni con il coach",
"measurements": "Misurazioni: peso, pressione, battito e ogni altro valore registrato da te o da un dispositivo",
"integrations": "Token di integrazione (cifrati)",
"security": "Attività dell'account e di sicurezza (sessioni, registro degli accessi)"
"security": "Attività dell'account e di sicurezza (sessioni, registro degli accessi)",
"profile": "Il tuo profilo, l'anamnesi e i dati della cartella sanitaria",
"modules": "Dati di ogni modulo: {list}"
},
"retention": {
"title": "Conservazione",
+4 -5
View File
@@ -6987,12 +6987,11 @@
},
"stored": {
"title": "저장되는 항목",
"measurements": "측정값 (체중, 혈압, 맥박, 혈당, 수면, 체성분)",
"medications": "약과 복용 기록",
"moodLabs": "기분 기록과 검사 결과",
"coach": "코치 대화",
"measurements": "측정값: 체중, 혈압, 맥박과 직접 또는 기기로 기록한 모든 값",
"integrations": "연동 토큰 (암호화됨)",
"security": "계정·보안 활동 (세션, 로그인 기록)"
"security": "계정·보안 활동 (세션, 로그인 기록)",
"profile": "프로필, 병력, 건강 기록 정보",
"modules": "모듈별 기록: {list}"
},
"retention": {
"title": "보관 기간",
+4 -5
View File
@@ -6987,12 +6987,11 @@
},
"stored": {
"title": "Co jest przechowywane",
"measurements": "Pomiary (waga, ciśnienie, tętno, glukoza, sen, skład ciała)",
"medications": "Leki i historia przyjmowania",
"moodLabs": "Wpisy nastroju i wyniki badań laboratoryjnych",
"coach": "Rozmowy z asystentem",
"measurements": "Pomiary: waga, ciśnienie, tętno i każda inna wartość zapisana przez ciebie lub urządzenie",
"integrations": "Tokeny integracji (szyfrowane)",
"security": "Aktywność konta i zabezpieczeń (sesje, dziennik logowań)"
"security": "Aktywność konta i zabezpieczeń (sesje, dziennik logowań)",
"profile": "Twój profil, wywiad medyczny i dane dokumentacji zdrowotnej",
"modules": "Wpisy poszczególnych modułów: {list}"
},
"retention": {
"title": "Retencja",
@@ -0,0 +1,59 @@
/**
* Settings → Data & privacy: "What's stored" is read off the module registry,
* so every module that keeps records is named and a new one joins the list
* without an edit here.
*/
import { renderToStaticMarkup } from "react-dom/server";
import { describe, expect, it, vi } from "vitest";
vi.mock("@tanstack/react-query", () => ({
useQuery: () => ({ data: undefined, isLoading: true, isError: false }),
useQueryClient: () => ({ invalidateQueries: vi.fn() }),
useMutation: () => ({ mutate: vi.fn(), isPending: false }),
}));
vi.mock("@/hooks/use-auth", () => ({
useAuth: () => ({ user: null, isAuthenticated: true, isLoading: false }),
}));
vi.mock("@/components/settings/security-sessions-card", () => ({
SecuritySessionsCard: () => null,
}));
vi.mock("@/components/settings/security-activity-card", () => ({
SecurityActivityCard: () => null,
}));
vi.mock("@/components/settings/trusted-devices-card", () => ({
TrustedDevicesCard: () => null,
}));
import { I18nProvider } from "@/lib/i18n/context";
import { MODULE_KEYS, MODULE_REGISTRY } from "@/lib/modules/registry";
import { PrivacySection, storedModuleLabelKeys } from "../privacy-section";
function render(): string {
return renderToStaticMarkup(
<I18nProvider initialLocale="en">
<PrivacySection />
</I18nProvider>,
);
}
describe("Data & privacy — what's stored", () => {
it("names every module that keeps records", () => {
const keys = storedModuleLabelKeys();
for (const key of MODULE_KEYS) {
const { category, labelKey } = MODULE_REGISTRY[key];
const holdsRecords = category !== "export" && category !== "integration";
expect(keys.includes(labelKey), key).toBe(holdsRecords);
}
});
it("lists documents, vaccinations, the cycle and workouts on the page", () => {
const html = render();
const row = html.match(/data-slot="privacy-stored-modules"[^>]*>([^<]*)</);
expect(row).not.toBeNull();
for (const label of ["Documents", "Vaccinations", "Workouts"]) {
expect(row![1]).toContain(label);
}
expect(row![1]).toMatch(/Cycle/);
expect(row![1]).not.toContain("Doctor report");
});
});
+29 -5
View File
@@ -34,6 +34,7 @@ import { SecurityActivityCard } from "@/components/settings/security-activity-ca
import { TrustedDevicesCard } from "@/components/settings/trusted-devices-card";
import { useAuth } from "@/hooks/use-auth";
import { useTranslations } from "@/lib/i18n/context";
import { MODULE_KEYS, MODULE_REGISTRY } from "@/lib/modules/registry";
import { queryKeys } from "@/lib/query-keys";
import { apiGet } from "@/lib/api/api-fetch";
@@ -50,8 +51,22 @@ interface PrivacySummary {
};
}
/**
* The modules that keep records of their own, read off the module registry
* so a new module joins the "What's stored" list without anyone editing it.
* The hand-kept list this replaced had stopped at six lines while documents,
* vaccinations, the cycle and workouts were all being stored. Two categories
* hold nothing of their own: an export assembles what other modules store,
* and the connector's tokens are covered by the integration line.
*/
export function storedModuleLabelKeys(): string[] {
return MODULE_KEYS.map((key) => MODULE_REGISTRY[key])
.filter((m) => m.category !== "export" && m.category !== "integration")
.map((m) => m.labelKey);
}
export function PrivacySection() {
const { t } = useTranslations();
const { t, locale } = useTranslations();
const { isAuthenticated } = useAuth();
const {
@@ -91,11 +106,20 @@ export function PrivacySection() {
icon={Database}
title={t("settings.privacy.stored.title")}
/>
<ul className="text-muted-foreground list-disc space-y-1 text-sm">
<ul
className="list-disc space-y-1 pl-5 text-sm"
data-testid="privacy-stored-list"
>
<li>{t("settings.privacy.stored.measurements")}</li>
<li>{t("settings.privacy.stored.medications")}</li>
<li>{t("settings.privacy.stored.moodLabs")}</li>
<li>{t("settings.privacy.stored.coach")}</li>
<li>{t("settings.privacy.stored.profile")}</li>
<li data-slot="privacy-stored-modules">
{t("settings.privacy.stored.modules", {
list: new Intl.ListFormat(locale, {
style: "long",
type: "conjunction",
}).format(storedModuleLabelKeys().map((key) => t(key))),
})}
</li>
<li>{t("settings.privacy.stored.integrations")}</li>
<li>{t("settings.privacy.stored.security")}</li>
</ul>