From 01a05e4265ebb41f37280ff1edb9b0693d77d354 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andr=C3=A9=20Bombeck?= Date: Sun, 10 May 2026 00:20:39 +0200 Subject: [PATCH] docs(audit): v1.4.16 settings naming + consolidation audit Single-pass audit of every /settings/ route + section component + i18n keys. Documents inventory, naming drift, top/bottom-split anti-patterns, duplicate functionality, settings-vs-admin scope rule, and the three follow-up refactor commits. AI section flagged for B2, Export section already handled by B7. Co-Authored-By: Claude Opus 4.7 (1M context) --- docs/audit/v1416-settings-audit.md | 320 +++++++++++++++++++++++++++++ 1 file changed, 320 insertions(+) create mode 100644 docs/audit/v1416-settings-audit.md diff --git a/docs/audit/v1416-settings-audit.md b/docs/audit/v1416-settings-audit.md new file mode 100644 index 000000000..7cafeab2e --- /dev/null +++ b/docs/audit/v1416-settings-audit.md @@ -0,0 +1,320 @@ +# v1.4.16 Phase B6 — Settings naming-audit + +Status: audit landed 2026-05-10 +Author: Marc + agent + +> Marc's specific direction (kickoff 2026-05-09 late evening): +> "Mir wäre einfach auch noch mal wichtig dass du über alle Einstellungen +> alle Namings gehst und einmal guckst, dass alles stringent ist, dass +> alles sinnig ist, dass alles logisch aufgebaut ist." +> +> Plus the new memory `feedback_settings_no_split.md` — no top/bottom +> split anti-pattern (B2 will fix the explicit AI-section instance). + +This audit is the single pass over **every** `/settings/` route + its +section component + its i18n keys. It documents what is wrong today and what +the v1.4.16 phase-B6 refactor commits do about it. AI section (`/settings/ai`) +and Export section (`/settings/export`) are intentionally **out of scope**: +B2 owns AI provider UX, B7 just shipped Export. + +--- + +## 1. Inventory + +| Slug | Route | Section component | Sidebar icon | i18n title key | i18n description key | +| ----------------- | ------------------------------------------- | -------------------------------------------------------- | --------------------- | ----------------------------------------- | ----------------------------------------------- | +| `account` | `/settings/account` | `account-section.tsx` (``) | `User` | `settings.sections.account.title` | `settings.sections.account.description` | +| `integrations` | `/settings/integrations` | `integrations-section.tsx` (``) | `Link2` | `settings.sections.integrations.title` | `settings.sections.integrations.description` | +| `notifications` | `/settings/notifications` | `notifications-section.tsx` (``) | `Bell` | `settings.sections.notifications.title` | `settings.sections.notifications.description` | +| `dashboard` | `/settings/dashboard` | `dashboard-section.tsx` (``) ⇢ `dashboard-layout-section.tsx` | `LayoutDashboard` | `settings.sections.dashboard.title` | `settings.sections.dashboard.description` | +| `thresholds` | `/settings/thresholds` | `thresholds-settings-section.tsx` (``) ⇢ `thresholds-section.tsx` | `SlidersHorizontal` | `settings.sections.thresholds.title` | `settings.sections.thresholds.description` | +| `ai` (out of scope) | `/settings/ai` | `ai-section.tsx` (``) | `Sparkles` | `settings.sections.ai.title` | `settings.sections.ai.description` | +| `api` | `/settings/api` | `api-section.tsx` (``) | `KeyRound` | `settings.sections.api.title` | `settings.sections.api.description` | +| `export` (out of scope) | `/settings/export` | `export-section.tsx` (``) | `Download` | `settings.sections.export.title` | `settings.sections.export.description` | +| `advanced` | `/settings/advanced` | `advanced-section.tsx` (``) | `Settings2` | `settings.sections.advanced.title` | `settings.sections.advanced.description` | +| `about` | `/settings/about` | `about-section.tsx` (``) | `Info` | `settings.sections.about.title` | `settings.sections.about.description` | + +The slug list lives at `src/components/settings/section-slugs.ts` (server-safe +constant) and is mirrored by the icon/title list at the top of +`src/components/settings/settings-shell.tsx`. + +### Top-level i18n namespace shape (current) + +The `settings.*` namespace inside `messages/{en,de}.json` mixes three styles: + +1. **`settings.sections..{title,description}`** — the modern, audited + shape introduced with the v1.4.x split. Owned by `` for + nav labels and by each section component for its `

` + subtitle. **This + is the one we standardise on.** +2. **`settings..`** — exists for `settings.about.*` and + `settings.ai.*` but not the others. Mostly correct. +3. **`settings.`** — every other key is flat: `settings.profile`, + `settings.passkeyName`, `settings.withings`, `settings.moodLogTitle`, + `settings.dangerZone`, `settings.exportJson`, `settings.doctorReport`, + etc. This is the legacy shape and the source of most of the + inconsistency. + +--- + +## 2. Inconsistencies & Anti-patterns + +### 2.1 Section-component naming drift + +| Component file | Default export | Comment | +| -------------------------------------- | --------------------------- | ---------------------------------------------------------------------------------------------------- | +| `account-section.tsx` | `AccountSection` | OK — slug + `-section.tsx` + `` | +| `integrations-section.tsx` | `IntegrationsSection` | OK | +| `notifications-section.tsx` | `NotificationsSection` | OK | +| `dashboard-section.tsx` | `DashboardSection` | OK — but ALSO has `dashboard-layout-section.tsx` (``) as the inner card. | +| `thresholds-settings-section.tsx` | `ThresholdsSettingsSection` | **Drift** — the slug is `thresholds`, the file is `thresholds-settings-section.tsx`, AND the inner card lives at `thresholds-section.tsx` (``). The "settings" infix is redundant (everything in `src/components/settings/` is already a settings component). | +| `ai-section.tsx` | `AiSection` | OK (out of scope) | +| `api-section.tsx` | `ApiSection` | OK | +| `export-section.tsx` | `ExportSection` | OK (out of scope) | +| `advanced-section.tsx` | `AdvancedSection` | OK | +| `about-section.tsx` | `AboutSection` | OK | + +**Decision (refactor commit 2):** the route-level wrapper is named after the +slug (``), the inner card keeps its own descriptive name +(``, `` post-refactor). +File layout normalises to: + +- `src/components/settings/-section.tsx` — wrapper with `

` + subtitle +- `src/components/settings/--section.tsx` — internal card + +That means renaming `thresholds-settings-section.tsx` → `thresholds-section.tsx` +**at the route boundary** and renaming the existing inner card from +`thresholds-section.tsx` → `thresholds-editor-section.tsx` so the names stop +clashing. + +### 2.2 i18n key naming drift + +The historic flat namespace (`settings.profile`, `settings.passkeyName`, …) +predates the section split. Result: the same concept is reachable under two +different keys. + +| Concept | Modern key | Legacy key still in code | +| ---------------------- | -------------------------------------------------------------- | ------------------------------------------------------ | +| Account page title | `settings.sections.account.title` | `settings.profile` *(card heading inside the page)* | +| AI page title | `settings.sections.ai.title` (= `settings.ai.title`) | `settings.kiInsights` (card heading) — duplicate | +| Withings card title | (none — only flat key) | `settings.withings` | +| Token actions | (none) | `settings.tokenRevoke`, `settings.tokenRevoked`, … | +| Doctor report | `settings.sections.export.cards.doctorReport.title` | `settings.doctorReport` (legacy) | +| Audit log | (none) | `settings.auditLog`, `settings.auditAction`, … | +| Danger zone | (none) | `settings.dangerZone`, `settings.dangerZoneTitle`, … | +| moodLog card title | (none) | `settings.moodLogTitle` | +| Notifications channels | (none) | `settings.telegram`, `settings.ntfy`, `settings.webPush` | + +**Decision (refactor commit 2):** +- Keep `settings.sections..{title,description}` as the canonical + source for `

` + subtitle. No change needed. +- Introduce `settings.sections..cards..title` / + `.description` / `.action` for major card headings within a section + (mirrors the export shape `settings.sections.export.cards.*` that B7 + already rolled out). +- Leave the existing flat keys as **deprecation aliases** for now. Add a + comment in JSON documenting that they are legacy. Don't churn every + call-site in this audit phase — that's a separate hygiene PR. + +### 2.3 Anti-pattern: section title vs sidebar label vs document `` + +| Slug | Sidebar label (DE) | Page `<h1>` (DE) | Browser `<title>` | Match? | +| ------------- | --------------------- | ----------------------- | ---------------------------- | ------ | +| account | "Konto" | "Konto" | (Next.js inherits root) | yes | +| integrations | "Integrationen" | "Integrationen" | " | yes | +| notifications | "Benachrichtigungen" | "Benachrichtigungen" | " | yes | +| dashboard | "Dashboard" | "Dashboard" | " | yes | +| thresholds | "Persönliche Zielwerte" | "Persönliche Zielwerte" | " | yes | +| ai | "KI-Auswertungen" | "KI-Insights" (legacy `settings.ai.title` = "KI-Insights") | " | **mismatch** — sidebar says "Auswertungen", page heading says "Insights" | +| api | "API & Tokens" | "API & Tokens" | " | yes | +| export | "Export" | "Export" | " | yes | +| advanced | "Erweitert" | "Erweitert" | " | yes | +| about | "Über" | "Über" | " | yes | + +The AI mismatch is **out of B6 scope** — B2 owns the AI section. Flagged +for B2. + +EN side: equivalent mismatch — sidebar "AI Insights", page `<h1>` "AI +Insights" via `settings.sections.ai.title` (which happens to equal +`settings.ai.title`). That one's consistent in EN; only DE diverges. + +### 2.4 Top/bottom split anti-pattern + +Per Marc's `feedback_settings_no_split.md`, there must be no instance where +a single concept is selected at the top of a section and configured at the +bottom. Audit: + +| Section | Top-bottom split? | Notes | +| ------------------ | ----------------- | ------------------------------------------------------------------------------------ | +| Account | No | One profile card, one passkey card, one password card, one tour card. Sequential, not split. | +| Integrations | No | Withings + moodLog cards, each self-contained. | +| Notifications | **Borderline** | `<NotificationStatusCard>` lists per-channel status at the top; the per-channel config cards (`<TelegramCard>`, `<NtfyCard>`, `<WebPushCard>`) follow below. The status card has `Re-enable` + `Send test` actions per channel that arguably duplicate functionality in the config cards. **Not a top/bottom-split-of-one-concept** — the status card is a read-only summary, the config cards are mutators. Keeping it. | +| Dashboard | No | One layout card. | +| Thresholds | No | One editor card with per-metric rows. | +| AI | **YES (out of scope, B2)** | Provider dropdown at the top selects between OpenAI/Anthropic/Local; key/credential inputs for each provider live further down. This is the case Marc named explicitly. | +| API | No | Endpoints reference card + tokens card. | +| Export | No | 5 cards, each self-contained. | +| Advanced | No | Single danger-zone card (post-B7 cleanup). | +| About | No | Identity + sources + updates cards. | + +**Decision:** AI flagged for B2 (already in roadmap). All other sections +are clean. Notifications kept as-is — the read-only-status-then-mutator +pattern is a deliberate choice, not a split anti-pattern. + +### 2.5 Duplicate functionality between sections + +| Concept | Owner section | Duplicated where? | Action | +| ----------------------- | ---------------------- | ------------------------------------------------------------------------------ | --------------------------------------- | +| Doctor-report PDF | Export (canonical) | Used to live in Advanced as `<ExportCard>`. **Already removed in B7.** | None. | +| CSV/JSON export buttons | Export (canonical) | Used to live in Advanced. **Already removed in B7.** | None. | +| Language selector | Account ⇢ profile card | NONE. | OK. | +| Channel test send | Notifications ⇢ status card | Each individual channel card (`<TelegramCard>`, etc.) ALSO has a test-send button. | Document — not removing; the status card surfaces it for failed channels (after auto-disable), the config cards have it as a "did I get the credentials right" smoke test. Different intents. | +| Withings credentials | Integrations ⇢ Withings card | NONE. | OK. | +| User-level settings vs admin-level settings | mostly disjoint | `settings.adminAreaTitle` / `settings.openAdminConsole` exist as keys but are **not currently rendered anywhere** in any settings section component. Stale. | Mark `settings.adminAreaTitle` etc. as deprecated in JSON. | + +### 2.6 Settings vs Admin: scope rule + +Settings are **per-user** (toggles that affect one account). Admin is +**system-wide** (toggles that affect every user, default policy, host +metrics, audit log read-access). Two cases overlap by design: + +- **Notifications**: per-channel global on/off lives in `/admin` (admin + decides if Telegram is allowed at all on the instance), per-user + channel credentials live in `/settings/notifications` (the user sets + their own bot token). The settings UI already reads + `/api/settings/global-services` and hides cards when an admin has + disabled the channel system-wide. **Correct division.** +- **AI providers**: same shape — admin sets a default key, user can + override with their own. Out of B6 scope, but the division is correct. + +No duplicate user-facing toggles found between `/admin` and `/settings`. +Keys like `settings.adminAreaTitle` / `settings.openAdminConsole` are +**dead code** — they were intended for a "shortcut to admin console" tile +in the user settings page that never shipped. **Marking deprecated.** + +### 2.7 "Missing settings" — features without a UI + +The following toggles or behaviours exist in the data layer but have no +end-user-visible setting today: + +| Feature | Where it lives | Should it have a setting? | +| ---------------------------------------- | --------------------------------------- | -------------------------------------------- | +| Onboarding-tour replay | already shipped in `<AccountSection>` (v1.4.15 B5) | already done | +| Achievements opt-out | server-side `<UserAchievement>` table | low priority — defer | +| AI raw-data toggle (`rawData`) | already in `<AiSection>` (out of scope) | already done | +| Personal medical-references override | server-side `users.medicalReferences` | defer (B5a referenced this — not yet user-tunable) | +| Per-metric reminder schedule | `Medication.schedule` | covered by medication editor, not settings | +| Locale fallback ("auto-detect off") | `i18n/config.ts` | low priority — defer | + +Nothing is shipped-without-UI that should be exposed in B6. The "deferred" +items belong in v1.5 product roadmap, not this audit. + +### 2.8 DE/EN translation quality + consistency + +Spot-checked: + +- `settings.sections.account.description` EN: "Profile, sign-in methods, and account deletion." — but the section also covers password change + onboarding tour. **Description undersells the scope.** Recommend rewrite. +- `settings.sections.ai.description` EN/DE: "Provider, model, key." / "Provider, Modell, API-Key." — too terse, doesn't say what the section does. Recommend rewrite. (Out of scope, B2.) +- `settings.sections.api.description` EN: "Bearer tokens for headless clients." / DE: "Bearer-Tokens für Drittanbieter-Clients." — `Drittanbieter` is overloaded (third-party SaaS, not headless tooling). Recommend "Bearer-Tokens für eigene Skripte und Apps." +- `settings.sections.advanced.description` EN: "Import, danger zone." — but post-B7 there's no Import surface here, only the danger zone. **Stale.** Recommend "Account-wide danger zone — wipe all health data." +- `settings.sections.notifications.description` EN: "Telegram, ntfy, Web Push." — fine; lists the channels. DE same shape. +- `settings.sections.thresholds.description` EN: "Override target ranges for every metric." — clear. +- `settings.sections.dashboard.description` EN: "Tile layout and order." — clear. +- `settings.sections.integrations.description` EN: "Withings, moodLog, and other connected services." — fine. + +**Action (refactor commit 2):** rewrite the three muddy descriptions +(`account`, `api`, `advanced`). + +--- + +## 3. Logical grouping review + +Marc asked: "are settings grouped sensibly?". Current order in the +sidebar: + +1. Account — personal identity + sign-in +2. Integrations — third-party data sources +3. Notifications — per-channel delivery +4. Dashboard — UI layout +5. Thresholds — target ranges (used by charts + insights) +6. AI — analysis provider +7. API — bearer tokens for headless clients +8. Export — data out +9. Advanced — danger zone +10. About — version + links + +This order roughly reads as: **identity → data flow in → output +configuration → automation → data flow out → housekeeping → meta**. +Defensible. No re-order required. + +One concept worth highlighting: **Thresholds + Dashboard are both UI +configuration**, but Thresholds also drives *insight* + *AI* outputs. +Putting Thresholds between Dashboard and AI keeps it close to both +concerns. Leaving as-is. + +--- + +## 4. Refactor plan (the three commits that follow this audit) + +Commit 1 (this commit) — `docs(audit): v1.4.16 settings naming + consolidation audit` + +Commit 2 — `refactor(settings): consistent naming + i18n key namespace` +- Rename `thresholds-settings-section.tsx` → `thresholds-section.tsx` + (route-level wrapper). +- Rename existing inner card from `thresholds-section.tsx` → + `thresholds-editor-section.tsx`. +- Update `src/app/settings/[section]/page.tsx` import. +- Rewrite `settings.sections.account.description`, + `settings.sections.api.description`, + `settings.sections.advanced.description` in EN + DE per the audit. +- Mark `settings.adminAreaTitle`, `settings.adminAreaDescription`, + `settings.openAdminConsole` as deprecated (JSON comment, no + call-site uses them). +- Add `settings.sections.<slug>.cards.*` aliases for the major card + headings (account profile/passkeys/password/tour; + notifications status/telegram/ntfy/webPush; integrations + withings/moodLog; api endpoints/tokens; advanced dangerZone). + Don't break the existing flat keys — sections still consume them + until a follow-up hygiene PR. + +Commit 3 — `refactor(settings): remove duplicate toggles, route to canonical owner` +- Document the settings-vs-admin scope rule in `CLAUDE.md`. +- No actual duplicate found that needs removing today; commit body + documents the audit conclusion ("no duplicate user-facing toggles + identified; settings keep per-user toggles, admin keeps system-wide + toggles; legacy `settings.adminAreaTitle` keys are stale and marked + deprecated"). + +Commit 4 — `test(settings): coverage for renamed/consolidated sections` +- Update `src/components/settings/__tests__/sections.test.tsx` to + import the renamed thresholds component and assert the new file + layout. +- Add an SSR smoke test that loops every slug in + `SETTINGS_SECTION_SLUGS` (excluding `ai` + `export` because their + components have heavy in-page state) and asserts: + - The page renders without throwing + - The `<h1>` resolves the localised title (no raw key leak) + - The subtitle resolves +- Add an i18n parity test asserting every `settings.sections.<slug>` + key has both `.title` and `.description` in both locales. + +--- + +## 5. Out-of-scope items flagged for follow-up + +- **B2** — `<AiSection>` provider top/bottom split + DE sidebar/heading + mismatch ("KI-Auswertungen" vs "KI-Insights"). +- **Hygiene PR (post-v1.4.16)** — migrate every flat `settings.<key>` + consumer to the new `settings.sections.<slug>.cards.*` shape, then + delete the legacy flat keys. +- **v1.5 roadmap** — surface achievements opt-out, personal + medical-references override, locale-detection toggle. + +--- + +## 6. Constraints honoured + +- No user-facing setting removed. +- No new dependency added. +- All new i18n keys land in EN + DE. +- Pre-existing tests stay green. +- No `--no-verify`, no `--no-gpg-sign`.