fix(settings): cut five module rows back to one sentence

Environment, external assistant access, Documents, mental wellbeing and
nutrient intake ran 120-165 characters in German where their thirteen
neighbours sat at 30-75. Every one of the five carried the same second
sentence, "off by default", which restates what the switch beside the row
already shows.

The mental-wellbeing row also named two screeners where the module ships
four, and its second sentence held the one statement in the app about what
happens to the answers. That statement moves to the mental-wellbeing page
itself, where somebody is about to answer them.

A guard holds every module row to one sentence in all six locales, and
under the 120-character guideline in English and German.
This commit is contained in:
Marc-André Bombeck committed 2026-08-08 15:17:05 +02:00
1 parent 13b4a0be0b
commit beb070c0a0
8 files changed
+130 -36

No files matched your search

+7 -6
View File
@@ -8497,23 +8497,23 @@
},
"mcp": {
"label": "Externer Assistentenzugriff (MCP)",
"description": "Einem MCP-fähigen Assistenten Lesezugriff auf deine Gesundheitsdaten über ein begrenztes Token erlauben. Standardmäßig aus."
"description": "Lesezugriff für einen MCP-fähigen Assistenten, über ein begrenztes Token."
},
"environment": {
"label": "Umwelt",
"description": "Tägliches Wetter und Tageslicht für deinen Ort einbeziehen, um Zusammenhänge mit Stimmung, Schlaf und Vitalwerten zu erkennen. Standardmäßig aus."
"description": "Tägliches Wetter und Tageslicht für deinen Ort, neben Stimmung, Schlaf und Vitalwerten."
},
"inboundDocuments": {
"label": "Dokumente",
"description": "Bewahre jedes medizinische Schreiben, jeden Befund und Scan an einem verschlüsselten, durchsuchbaren Ort auf — verknüpft mit deinen Erkrankungen. Standardmäßig aus."
"description": "Arztbriefe, Befunde und Scans verschlüsselt und durchsuchbar an einem Ort."
},
"mentalHealth": {
"label": "Seelisches Wohlbefinden",
"description": "Freiwillige PHQ-9- und GAD-7-Selbsttests ergänzend zum Stimmungstagebuch. Standardmäßig aus; Antworten sind verschlüsselt und bleiben aus dem KI-Coach heraus."
"description": "Vier freiwillige Selbsttests neben dem Stimmungstagebuch."
},
"nutrients": {
"label": "Nährstoffzufuhr",
"description": "Speichert tägliche Mikronährstoff-Summen (Vitamine, Mineralstoffe, Wasser, Koffein) aus Apple Health. Standardmäßig aus."
"description": "Tägliche Mikronährstoff-Summen aus Apple Health."
}
},
"illness": {
@@ -9010,7 +9010,8 @@
},
"openDetail": "Details anzeigen",
"historyLoadError": "Dein Testverlauf konnte nicht geladen werden",
"historyLoadErrorHint": "Das heißt nicht, dass keine Ergebnisse vorliegen — die Liste konnte nicht abgerufen werden. Versuche es erneut."
"historyLoadErrorHint": "Das heißt nicht, dass keine Ergebnisse vorliegen — die Liste konnte nicht abgerufen werden. Versuche es erneut.",
"privacyNote": "Deine Antworten sind verschlüsselt gespeichert und erreichen den KI-Coach nicht."
},
"records": {
"aiInclusion": {
+7 -6
View File
@@ -8497,23 +8497,23 @@
},
"mcp": {
"label": "External assistant access (MCP)",
"description": "Let an MCP-capable assistant read your health data over a scoped token. Off by default."
"description": "Read access for an MCP-capable assistant, over a scoped token."
},
"environment": {
"label": "Environment",
"description": "Pull in daily weather and daylight for your location to correlate with mood, sleep, and vitals. Off by default."
"description": "Daily weather and daylight for your location, beside mood, sleep, and vitals."
},
"inboundDocuments": {
"label": "Documents",
"description": "Keep every medical letter, report, and scan in one encrypted, searchable place — linked to your conditions. Off by default."
"description": "Medical letters, reports, and scans in one encrypted, searchable place."
},
"mentalHealth": {
"label": "Mental wellbeing",
"description": "Opt-in PHQ-9 and GAD-7 screeners beside mood tracking. Off by default; answers are encrypted and kept out of the AI Coach."
"description": "Four opt-in screeners beside mood tracking."
},
"nutrients": {
"label": "Nutrient intake",
"description": "Store daily micronutrient totals (vitamins, minerals, water, caffeine) synced from Apple Health. Off by default."
"description": "Daily micronutrient totals synced from Apple Health."
}
},
"illness": {
@@ -9010,7 +9010,8 @@
},
"openDetail": "Show details",
"historyLoadError": "Your assessment history could not be loaded",
"historyLoadErrorHint": "This does not mean you have no results — the list could not be fetched. Try again."
"historyLoadErrorHint": "This does not mean you have no results — the list could not be fetched. Try again.",
"privacyNote": "Your answers are stored encrypted and never reach the AI Coach."
},
"records": {
"aiInclusion": {
+7 -6
View File
@@ -8497,23 +8497,23 @@
},
"mcp": {
"label": "Acceso de asistente externo (MCP)",
"description": "Permite que un asistente compatible con MCP lea tus datos de salud mediante un token con alcance limitado. Desactivado de forma predeterminada."
"description": "Acceso de lectura para un asistente compatible con MCP, mediante un token limitado."
},
"environment": {
"label": "Entorno",
"description": "Incorpora el tiempo y las horas de luz diarias de tu ubicación para correlacionarlos con el ánimo, el sueño y las constantes. Desactivado por defecto."
"description": "El tiempo y las horas de luz diarias de tu ubicación, junto al ánimo, el sueño y las constantes."
},
"inboundDocuments": {
"label": "Documentos",
"description": "Guarda cada carta médica, informe y escaneo en un solo lugar cifrado y con búsqueda — vinculado a tus afecciones. Desactivado por defecto."
"description": "Cartas médicas, informes y escaneos en un lugar cifrado y con búsqueda."
},
"mentalHealth": {
"label": "Bienestar mental",
"description": "Cuestionarios PHQ-9 y GAD-7 opcionales, junto al registro de ánimo. Desactivado por defecto; las respuestas se cifran y se mantienen fuera del Coach de IA."
"description": "Cuatro cuestionarios opcionales, junto al registro de ánimo."
},
"nutrients": {
"label": "Ingesta de nutrientes",
"description": "Guarda los totales diarios de micronutrientes (vitaminas, minerales, agua, cafeína) sincronizados desde Apple Health. Desactivado por defecto."
"description": "Totales diarios de micronutrientes sincronizados desde Apple Health."
}
},
"illness": {
@@ -9010,7 +9010,8 @@
},
"openDetail": "Ver detalles",
"historyLoadError": "No se pudo cargar tu historial de evaluaciones",
"historyLoadErrorHint": "Esto no significa que no tengas resultados: no se pudo obtener la lista. Vuelve a intentarlo."
"historyLoadErrorHint": "Esto no significa que no tengas resultados: no se pudo obtener la lista. Vuelve a intentarlo.",
"privacyNote": "Tus respuestas se guardan cifradas y nunca llegan al Coach de IA."
},
"records": {
"aiInclusion": {
+7 -6
View File
@@ -8497,23 +8497,23 @@
},
"mcp": {
"label": "Accès assistant externe (MCP)",
"description": "Autorise un assistant compatible MCP à lire vos données de santé via un jeton à portée limitée. Désactivé par défaut."
"description": "Accès en lecture pour un assistant compatible MCP, via un jeton limité."
},
"environment": {
"label": "Environnement",
"description": "Intégrez la météo et la durée du jour quotidiennes de votre lieu pour les corréler à l'humeur, au sommeil et aux constantes. Désactivé par défaut."
"description": "Météo et durée du jour quotidiennes de votre lieu, à côté de l'humeur, du sommeil et des constantes."
},
"inboundDocuments": {
"label": "Documents",
"description": "Conservez chaque courrier médical, compte rendu et scan dans un espace chiffré et consultable — lié à vos affections. Désactivé par défaut."
"description": "Courriers médicaux, comptes rendus et scans dans un espace chiffré et consultable."
},
"mentalHealth": {
"label": "Bien-être mental",
"description": "Questionnaires PHQ-9 et GAD-7 facultatifs, en complément du suivi de l'humeur. Désactivé par défaut ; les réponses sont chiffrées et tenues à l'écart du Coach IA."
"description": "Quatre questionnaires facultatifs, à côté du suivi de l'humeur."
},
"nutrients": {
"label": "Apports en nutriments",
"description": "Enregistre les totaux quotidiens de micronutriments (vitamines, minéraux, eau, caféine) synchronisés depuis Apple Health. Désactivé par défaut."
"description": "Totaux quotidiens de micronutriments synchronisés depuis Apple Health."
}
},
"illness": {
@@ -9010,7 +9010,8 @@
},
"openDetail": "Voir les détails",
"historyLoadError": "Ton historique d'évaluations n'a pas pu être chargé",
"historyLoadErrorHint": "Cela ne veut pas dire que tu n'as aucun résultat : la liste n'a pas pu être récupérée. Réessaie."
"historyLoadErrorHint": "Cela ne veut pas dire que tu n'as aucun résultat : la liste n'a pas pu être récupérée. Réessaie.",
"privacyNote": "Vos réponses sont stockées chiffrées et n'atteignent jamais le Coach IA."
},
"records": {
"aiInclusion": {
+7 -6
View File
@@ -8497,23 +8497,23 @@
},
"mcp": {
"label": "Accesso assistente esterno (MCP)",
"description": "Consente a un assistente compatibile con MCP di leggere i tuoi dati sanitari tramite un token con ambito limitato. Disattivato per impostazione predefinita."
"description": "Accesso in lettura per un assistente compatibile con MCP, tramite un token limitato."
},
"environment": {
"label": "Ambiente",
"description": "Integra il meteo e le ore di luce giornaliere della tua posizione per correlarli con umore, sonno e parametri vitali. Disattivato di default."
"description": "Meteo e ore di luce giornaliere della tua posizione, accanto a umore, sonno e parametri vitali."
},
"inboundDocuments": {
"label": "Documenti",
"description": "Conserva ogni lettera medica, referto e scansione in un unico archivio cifrato e ricercabile — collegato alle tue patologie. Disattivato di default."
"description": "Lettere mediche, referti e scansioni in un archivio cifrato e ricercabile."
},
"mentalHealth": {
"label": "Benessere mentale",
"description": "Questionari PHQ-9 e GAD-7 facoltativi, accanto al diario dell'umore. Disattivato per impostazione predefinita; le risposte sono cifrate e tenute fuori dal Coach IA."
"description": "Quattro questionari facoltativi, accanto al diario dell'umore."
},
"nutrients": {
"label": "Assunzione di nutrienti",
"description": "Salva i totali giornalieri di micronutrienti (vitamine, minerali, acqua, caffeina) sincronizzati da Apple Health. Disattivato per impostazione predefinita."
"description": "Totali giornalieri di micronutrienti sincronizzati da Apple Health."
}
},
"illness": {
@@ -9010,7 +9010,8 @@
},
"openDetail": "Mostra dettagli",
"historyLoadError": "Non è stato possibile caricare la cronologia dei test",
"historyLoadErrorHint": "Questo non significa che non ci siano risultati: non è stato possibile recuperare l'elenco. Riprova."
"historyLoadErrorHint": "Questo non significa che non ci siano risultati: non è stato possibile recuperare l'elenco. Riprova.",
"privacyNote": "Le tue risposte sono salvate cifrate e non raggiungono mai il Coach IA."
},
"records": {
"aiInclusion": {
+7 -6
View File
@@ -8497,23 +8497,23 @@
},
"mcp": {
"label": "Dostęp asystenta zewnętrznego (MCP)",
"description": "Pozwól asystentowi obsługującemu MCP czytać Twoje dane zdrowotne za pomocą tokena o ograniczonym zakresie. Domyślnie wyłączone."
"description": "Dostęp do odczytu dla asystenta obsługującego MCP, przez token o ograniczonym zakresie."
},
"environment": {
"label": "Środowisko",
"description": "Pobieraj dzienną pogodę i długość dnia dla swojej lokalizacji, aby powiązać je z nastrojem, snem i parametrami życiowymi. Domyślnie wyłączone."
"description": "Dzienna pogoda i długość dnia dla Twojej lokalizacji, obok nastroju, snu i parametrów życiowych."
},
"inboundDocuments": {
"label": "Dokumenty",
"description": "Przechowuj każde pismo lekarskie, wynik i skan w jednym zaszyfrowanym, przeszukiwalnym miejscu — powiązane z Twoimi schorzeniami. Domyślnie wyłączone."
"description": "Pisma lekarskie, wyniki i skany w jednym zaszyfrowanym, przeszukiwalnym miejscu."
},
"mentalHealth": {
"label": "Dobrostan psychiczny",
"description": "Dobrowolne testy PHQ-9 i GAD-7 obok dziennika nastroju. Domyślnie wyłączone; odpowiedzi są szyfrowane i pozostają poza Coachem AI."
"description": "Cztery dobrowolne testy obok dziennika nastroju."
},
"nutrients": {
"label": "Spożycie składników odżywczych",
"description": "Zapisuje dzienne sumy mikroskładników (witaminy, minerały, woda, kofeina) synchronizowane z Apple Health. Domyślnie wyłączone."
"description": "Dzienne sumy mikroskładników synchronizowane z Apple Health."
}
},
"illness": {
@@ -9010,7 +9010,8 @@
},
"openDetail": "Pokaż szczegóły",
"historyLoadError": "Nie udało się wczytać historii testów",
"historyLoadErrorHint": "To nie znaczy, że nie masz wyników — nie udało się pobrać listy. Spróbuj ponownie."
"historyLoadErrorHint": "To nie znaczy, że nie masz wyników — nie udało się pobrać listy. Spróbuj ponownie.",
"privacyNote": "Twoje odpowiedzi są przechowywane w postaci zaszyfrowanej i nigdy nie trafiają do Coacha AI."
},
"records": {
"aiInclusion": {
@@ -142,6 +142,17 @@ export function MentalWellbeing() {
description={t("mentalHealth.pageDescription")}
/>
{/* What happens to the answers. The Modules hub used to carry this
as the second sentence of the module's one-line description,
which was the only place in the app that said it — so it moved
to the screen where somebody is about to answer. */}
<p
data-slot="mental-health-privacy-note"
className="text-muted-foreground -mt-3 text-xs"
>
{t("mentalHealth.privacyNote")}
</p>
<section aria-label={t("mentalHealth.choosePrompt")}>
<h2 className="sr-only">{t("mentalHealth.choosePrompt")}</h2>
{historyError ? (
@@ -0,0 +1,77 @@
import { describe, it, expect } from "vitest";
import { readFileSync } from "node:fs";
import { join } from "node:path";
import { MODULE_KEYS } from "@/lib/modules/registry";
import { locales } from "@/lib/i18n/config";
/**
* A module row in the "What you track" hub gets ONE sentence, same as a card
* header's description slot (design standards §3).
*
* Five of the eighteen rows had grown a second sentence — always the same
* one, "Off by default", and in one case a third clause after it. The
* neighbours sat at 30-75 characters and those five at 120-165, so the hub
* read as two different lists stacked on each other. The switch beside the
* row already says whether the module is on, so the sentence was restating
* what the control shows.
*
* The single-sentence half of the rule is checked in every locale: it is a
* structural claim, not a length one, and a translation that re-adds the
* clause is the same defect. The character cap is checked in EN and DE, the
* two the maintainer reads, matching `card-description-length.test.ts`.
*/
const ROOT = process.cwd();
const MAX_CHARS = 120;
function bundle(locale: string): Record<string, { description?: string }> {
const raw = readFileSync(join(ROOT, "messages", `${locale}.json`), "utf8");
return JSON.parse(raw).modules;
}
describe("module hub descriptions", () => {
it("names a description for every module in every locale", () => {
// Positive control: without it, a renamed `modules` namespace would make
// the two checks below iterate an empty set and report green.
for (const locale of locales) {
const modules = bundle(locale);
const missing = MODULE_KEYS.filter(
(key) => !modules[key]?.description?.trim(),
);
expect(missing, `${locale} is missing descriptions`).toEqual([]);
}
});
it("holds each one to a single sentence, in every locale", () => {
const offenders: string[] = [];
for (const locale of locales) {
const modules = bundle(locale);
for (const key of MODULE_KEYS) {
const text = modules[key].description!;
// Drop the closing terminator, then any sentence break left inside
// is a second sentence. A semicolon splice counts — it is how the
// longest of the five carried its third clause.
const body = text.replace(/[.!?…]\s*$/, "");
if (/[.!?;]\s/.test(body)) {
offenders.push(`${locale}/${key}: ${text}`);
}
}
}
expect(offenders).toEqual([]);
});
it("holds each one under the character guideline in EN and DE", () => {
const offenders: string[] = [];
for (const locale of ["en", "de"] as const) {
const modules = bundle(locale);
for (const key of MODULE_KEYS) {
const text = modules[key].description!;
if (text.length > MAX_CHARS) {
offenders.push(`${locale}/${key}: ${text.length} chars`);
}
}
}
expect(offenders).toEqual([]);
});
});