From 14eff9647445c438041bd848b38d682ab5478740 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andr=C3=A9=20Bombeck?= Date: Sun, 10 May 2026 14:01:38 +0200 Subject: [PATCH] docs(planning): Phase B (A8 application) report + v1.4.20 backlog Wave B completed: 6 / 6 CRITICAL applied, 21 / 25 HIGH applied, 4 HIGH deferred (3 not-an-issue, 1 owned by v1.4.20 Insights redesign), 31 MED + 16 LOW seeded as v1.4.20 backlog. Final verification clean. STATE.md status block appended; A8 raw findings added to repo for future reference. Co-Authored-By: Claude Opus 4.7 (1M context) --- .planning/STATE.md | 54 ++- .planning/phase-A8-quality-findings.md | 524 +++++++++++++++++++++++++ .planning/phase-B-quality-report.md | 82 ++++ .planning/v1420-backlog.md | 214 ++++++++++ 4 files changed, 871 insertions(+), 3 deletions(-) create mode 100644 .planning/phase-A8-quality-findings.md create mode 100644 .planning/phase-B-quality-report.md create mode 100644 .planning/v1420-backlog.md diff --git a/.planning/STATE.md b/.planning/STATE.md index 54b8c3e93..bf590d190 100644 --- a/.planning/STATE.md +++ b/.planning/STATE.md @@ -189,10 +189,25 @@ Last update: 2026-05-10T12:48+02:00 - [ ] Output prioritized list for inline fixing - Detailed report: `.planning/phase-A8-quality-findings.md` -## Wave B — Apply A8 findings (after A8 completes) +## Wave B — Apply A8 findings ✅ -- [ ] Fix CRITICAL/HIGH from quality-of-life audit -- Detailed report: `.planning/phase-B-report.md` +- [x] 6 / 6 CRITICAL fixed: F-01 (time-window locale), F-02 + (login-overview filter), F-03 (insulting badges), F-04 + (date-input lang), F-05 (sidebar copy), F-06 (legacy CTA + verb) +- [x] 21 / 25 HIGH fixed: F-07, F-08 (partial), F-09, F-10, + F-11, F-13, F-15, F-16, F-17, F-18, F-19, F-20, F-21, + F-22, F-24, F-25, F-27, F-28, F-29, F-30, F-31. F-12, + F-14, F-23, F-26 deferred (3 not-an-issue / overlap with + Wave A, 1 owned by v1.4.20 Insights redesign). +- [x] 31 MED + 16 LOW deferred to `.planning/v1420-backlog.md` +- [x] `pnpm typecheck` clean, `pnpm lint` 12 baseline warnings + / 0 new, `pnpm test --run` 1669/1669, `pnpm test:integration` + 67/67. Format-check shows pre-existing `.planning/*` + + `docs/audit/*` warnings only. +- 16 atomic commits on origin/main, no `--no-verify` / no + `--no-gpg-sign`, pre-commit hooks green on every push. +- Detailed report: `.planning/phase-B-quality-report.md` ## Wave D — Multi-agent QA + Product-Lead review @@ -322,6 +337,39 @@ medications.csv,mood.csv}/`) left in place — same call as v1.4.16 / `e2e/settings-mobile-consistency.spec.ts`, `.planning/phase-A6-{settings-audit,report}.md`. +- 2026-05-10T13:59+02:00 — Wave B / A8 application complete. + Applied 6 / 6 CRITICAL findings (F-01 through F-06) and 21 + / 25 HIGH findings (4 deferred: F-12 already a11y-clean, + F-14 shipped via A5, F-23 already i18n'd in DE, F-26 owned + by v1.4.20 Insights redesign). 31 MED + 16 LOW carried over + to `.planning/v1420-backlog.md` for the strategic v1.4.20 + pick-list. Sixteen atomic commits on origin/main: `180f46c` + (F-01 time-window locale), `dc75fe8` (F-02 login-overview + auth filter), `7a6bc81` (F-03 insulting achievement titles), + `ff6e184` (F-04 date-input lang attribute), `4feacad` (F-05 + admin sidebar copy), `d263125` (F-06 legacy CTA verb), + `1ae9b9d` (F-09 + F-10 raw enum badges), `42b0f11` (F-11 + role-change button label + aria-label), `1f4d6a2` (F-07 + threshold naming), `3f6e670` (F-15 + F-19 + F-20 + F-21 + + F-22 + F-25 + F-27 + F-29 + F-30 i18n copy sweep), `70ebe32` + (F-08 partial — danger-zone + feedback dedupe), `98cd13c` + (F-24 + F-28 + F-52 achievements + admin/integrations + toggles), `876e074` (F-13 mobile sys/dia + F-16 telegram + badge collapse), `bc81fd7` (F-17 audit action labels + F-31 + recent-activity row links), `713b494` (F-18 token name + ISO-suffix renderer), `ae2f671` (prettier on touched + files). Pre-commit hooks green on every push, no + `--no-verify` / no `--no-gpg-sign`. Final verification: + `pnpm typecheck` clean, `pnpm lint` 0 errors / 12 baseline + warnings, `pnpm test --run` 1669 / 1669 (up +11 from new + TDD cases: time-window-format 6, achievements-no-insults + 4, login-overview-auth-filter 1), `pnpm test:integration` + 67 / 67. Format-check shows 8 pre-existing + `.planning/*` + `docs/audit/*` files dirty (baseline noise + on `origin/main`, untouched). Detailed report: + `.planning/phase-B-quality-report.md`. Backlog: + `.planning/v1420-backlog.md`. + --- ## Previous milestone — v1.4.18 (completed 2026-05-10T11:45+02:00) diff --git a/.planning/phase-A8-quality-findings.md b/.planning/phase-A8-quality-findings.md new file mode 100644 index 000000000..886d27227 --- /dev/null +++ b/.planning/phase-A8-quality-findings.md @@ -0,0 +1,524 @@ +# Phase A8 — Quality-of-Life Audit (write-only) + +Run on production https://healthlog.bombeck.io with Marc's session, captured at Pixel 5 (mobile) and 1280px (desktop) via Playwright + code grep cross-check. The user is on the **English** locale, but several findings are German-only (translations, formatters) and were verified against `messages/de.json` + helper modules. + +Findings are sorted by **Severity DESC**, then **Effort ASC** within each severity. Top-10 most-impactful are marked with a star. + +Legend: CRIT = genuinely confusing / broken UX, HIGH = visible quality issue, MED = polish nit, LOW = taste preference. S/M/L = Small/Medium/Large. + +--- + +## CRITICAL findings + +### F-01 ⭐ — `formatTimeWindowRange` hard-codes German "bis ... Uhr" regardless of locale + +- Severity: CRITICAL · Type: copy-incorrect · Effort: S · Route: /medications, dashboard medication tile +- File: `src/lib/time-window-format.ts:10` +- Issue: Returns `"${start} bis ${end} Uhr"` literally, so an English-locale user sees `"Today, 19:00 bis 23:00 Uhr (Abends)"` (mixed languages, plus the German label `(Abends)` from i18n). Visible on /medications, dashboard, doctor report. +- Fix: Route through `useFormatters()` / per-locale string ("from {start} to {end}" / "{start} bis {end} Uhr"), or accept the locale as a parameter. + +### F-02 ⭐ — `/admin/login-overview` shows insights events instead of auth events + +- Severity: CRITICAL · Type: copy-incorrect (subtitle vs data) · Effort: S · Route: /admin/login-overview +- File: `src/components/admin/login-overview-section.tsx`, API at `src/app/api/admin/login-overview/route.ts` +- Issue: Page subtitle reads "Audit trail of authentication and admin events." but the rows are `insights.weight-status.en`, `insights.bmi-status.en`, `insights.medication-compliance-status.en` etc. Either the filter is missing (fetching all annotated events) or the subtitle is wrong. +- Fix: Either filter to `auth.*` + `admin.*` action prefixes, or rename the page "Activity / Audit trail" and surface the action category. + +### F-03 ⭐ — Achievement names "Idiot" and "Lazy Boy" are insulting and not translated + +- Severity: CRITICAL · Type: copy-incorrect · Effort: S · Route: /achievements +- File: `messages/en.json:2014, 2018` and `messages/de.json:2014, 2018` +- Issue: `overIntake1.title = "Idiot"` and `skippedIntake1.title = "Lazy Boy"` literally insult the user for over-taking medication or skipping a dose. Both are also untranslated in the German bundle. For a health app this is harmful. +- Fix: Rename to neutral "Double take" / "Took it twice" and "Stepped back" / "Kept it light"; translate. + +### F-04 ⭐ — Date format leaks US (mm/dd/yyyy) on a German-default app + +- Severity: CRITICAL · Type: copy-incorrect · Effort: M · Route: /admin/users, /admin/api-tokens, /admin/backups, /settings/account, /settings/export, onboarding +- File: many — likely `useFormatters()` not wired up everywhere; `` shows browser default +- Issue: Native date inputs render `mm/dd/yyyy` even when DE locale is active. Date columns rendered with `toLocaleDateString()` show `02/20/2026` style. DE users expect `20.02.2026` and `TT.MM.JJJJ` placeholders. +- Fix: Pass `lang="de-DE"` on ``, route all date display through `useFormatters().date()` with the active locale. + +### F-05 ⭐ — Admin "Welcome, Marc" tells users to "Pick a section from the sidebar" but mobile has no sidebar + +- Severity: CRITICAL · Type: copy-incorrect (mobile-only) · Effort: S · Route: /admin +- File: `messages/de.json:1813` / `messages/en.json:1813` (`admin.welcomeSubtitle`) +- Issue: Copy "Pick a section from the sidebar to manage HealthLog." but mobile uses a horizontal pill strip, not a sidebar. Also the en.json text is in DE. +- Fix: Detect mobile or use neutral copy: "Pick a section above to manage HealthLog." / "Wähle einen Bereich, um HealthLog zu verwalten."; ensure en.json has the English text. + +### F-06 — `/settings/notifications` info banner says "regenerate" but the only button says "Start analysis" + +- Severity: CRITICAL · Type: copy-incorrect · Effort: S · Route: /insights (yellow info card) +- File: likely `src/components/insights/*` (`insightsUpdated…` translation key) +- Issue: Yellow banner reads "Insights updated — regenerate for new explainability features" then offers a button labelled "Start analysis". Mismatch between verb ("regenerate") and CTA ("Start analysis"). +- Fix: Change either the banner copy ("Click 'Start analysis' to refresh with new explainability features.") or rename the button to "Regenerate". + +--- + +## HIGH findings + +### F-07 ⭐ — Settings nav label "Personal targets" but URL is /settings/thresholds and inner card title "Personal thresholds" + +- Severity: HIGH · Type: copy-redundant + copy-incorrect · Effort: S · Route: /settings/thresholds +- File: `src/components/settings/section-slugs.ts`, `src/components/settings/thresholds-editor-section.tsx`, `messages/*.json` +- Issue: Three different names for the same screen: nav "Personal targets" → page header "Personal targets" → card header "Personal thresholds". Two near-identical descriptions stack: subtitle "Override target ranges for every metric." + card help "Override the computed target range for any metric. Defaults stay in effect until you set a value here." +- Fix: Pick one term ("Personal thresholds" matches the route slug) and drop the duplicate help line. + +### F-08 ⭐ — Admin overview greeting + every admin/settings card has duplicate page header / card header + +- Severity: HIGH · Type: copy-redundant · Effort: S · Routes: /admin (Administration / Welcome), /admin/system-status (System Status / System Status), /admin/login-overview (Login Overview / Login Overview), /admin/danger-zone (Danger Zone / Danger Zone), /settings/dashboard (Dashboard / Dashboard layout), /admin/feedback (Feedback / Feedback Inbox), /admin/api-tokens (API Tokens / API Tokens) +- Issue: When a section has only one card, the card header repeats the page header verbatim, taking up vertical space without adding info. +- Fix: When section has exactly one card, drop the card header and keep only the page header (or vice-versa). + +### F-09 ⭐ — Backup type rendered as raw enum `WEEKLY_AUTO` (SCREAMING_SNAKE) + +- Severity: HIGH · Type: copy-incorrect · Effort: S · Route: /admin/backups +- File: `src/components/admin/backups-section.tsx:484` — `{row.type}` +- Issue: `row.type` is the raw enum `WEEKLY_AUTO`. Should be humanized. +- Fix: Map to translation key — `t('admin.backups.type.WEEKLY_AUTO')` → "Wöchentlich (auto)" / "Weekly (auto)". + +### F-10 ⭐ — Measurement source `WITHINGS` rendered ALL CAPS + +- Severity: HIGH · Type: copy-incorrect · Effort: S · Route: /measurements +- File: `src/components/measurements/measurement-list.tsx:419` — `{m.source}` +- Issue: Source enum value rendered raw — "WITHINGS" instead of "Withings". Mood page does it right ("moodLog"). +- Fix: Map enum to display string ("Withings", "Manuell" / "Manual", "moodLog"). + +### F-11 ⭐ — User-management role-change buttons "→ User" / "→ Admin" with no label + +- Severity: HIGH · Type: aria-missing + copy-missing · Effort: S · Route: /admin/users +- File: `src/components/admin/user-management-section.tsx` +- Issue: A bare arrow `→ User` / `→ Admin` is a button to demote/promote. No tooltip, no aria-label, ambiguous direction. +- Fix: Use explicit text "Make user" / "Make admin" (or "Zum Benutzer / Zum Admin"), add `aria-label` and a confirmation dialog. + +### F-12 ⭐ — User management mobile cards show 5 icon buttons with no labels (edit/passkey/lock/sign-out) + +- Severity: HIGH · Type: aria-missing + tooltip-missing · Effort: M · Route: /admin/users +- Issue: Pencil, key, log-out-arrow, shield → all icon-only with no `aria-label` or tooltip. Even the role-change arrow lives inside this row. +- Fix: Add `` + `aria-label` for every action icon. Consider collapsing to a `…` overflow menu on mobile. + +### F-13 — Mobile measurement list cannot distinguish systolic vs diastolic (same heart icon) + +- Severity: HIGH · Type: icon-mismatch · Effort: S · Route: /measurements (mobile) +- File: `src/components/measurements/measurement-list.tsx` +- Issue: Both `117 mmHg` (systolic) and `79 mmHg` (diastolic) render with the identical pink heart icon. User cannot tell them apart on mobile cards (desktop shows badges "Sys" / "Dia"). +- Fix: Add a "Sys" / "Dia" pill before the value on mobile, or use heart-up vs heart-down icons. + +### F-14 — Settings/Integrations status duplicated on the same card (this is also Marc's A5 concern) + +- Severity: HIGH · Type: copy-redundant · Effort: S · Route: /settings/integrations +- File: `src/components/settings/integrations-section.tsx` +- Issue: "Connected" badge top-right + "Last sync: 05/10/2026, 12:00" top-right tag + a second container inside the card with another "Connected" badge + "Last successful sync" + "Last attempt". Three places that say the same thing. (Already in A5 scope but flagging the duplication explicitly.) +- Fix: Single status tag top-right "Connected · 2 min ago"; drop the inner container. + +### F-15 — `/settings/notifications` mentions ntfy in subtitle but channel-reliability list shows only Telegram + Web Push + +- Severity: HIGH · Type: copy-incorrect · Effort: S · Route: /settings/notifications +- Issue: Subtitle "Per-channel delivery — Telegram, ntfy, Web Push — plus the live channel-reliability board." but the live board shows only 2 channels because ntfy isn't configured. Either show ntfy "Not configured" or drop ntfy from the subtitle. +- Fix: Render every channel slot regardless of state, with a "Not configured" badge for unconfigured channels. + +### F-16 — Notifications page "Enabled" + "Configured" badges next to Telegram header are redundant + +- Severity: HIGH · Type: copy-redundant · Effort: S · Route: /settings/notifications +- Issue: A pill "Configured" (green) and another "Enabled" (white) sit side-by-side with no visual hierarchy. Users don't know what differs. +- Fix: Single status badge "Active · sent 12 min ago" or "Configured · disabled". + +### F-17 — Login Overview action `auth.token.autoissue.native` leaks raw event-key in /admin recent activity + +- Severity: HIGH · Type: copy-incorrect · Effort: S · Route: /admin (Recent activity card) +- File: `src/components/admin/recent-audit-preview.tsx` +- Issue: One row in Recent activity shows `auth.token.autoissue.native` while peers show "Passkey login" / "Password login" — the dev-facing event key wasn't translated. +- Fix: Add a translation key for every audit-action enum and fall back to "Activity" instead of the raw key. + +### F-18 — `/admin/api-tokens` token name `iOS auto-login 2026-05-05T19:46:20.603Z` is dev-ugly + +- Severity: HIGH · Type: typography-inconsistent · Effort: S · Route: /admin/api-tokens, /settings/api +- Issue: ISO timestamp suffix in the token name leaks. Reads as a debug label. +- Fix: Format as "iOS auto-login · 05.05.2026 19:46" (locale-aware) when generating, or strip the suffix in the renderer. + +### F-19 — `/admin/api-tokens` Permissions column shows raw `*` with no explanation + +- Severity: HIGH · Type: copy-missing · Effort: S · Route: /admin/api-tokens, /settings/api +- File: `src/components/admin/api-token-overview-section.tsx` +- Issue: Permissions cell renders `*` (literally) — a regex/glob meaning "all". Cryptic. +- Fix: Render as "All" / "Alle" badge with a tooltip listing wildcard semantics. + +### F-20 — `/admin/feedback` subtitle and card subtitle say almost the same thing twice + +- Severity: HIGH · Type: copy-redundant · Effort: S · Route: /admin/feedback +- Issue: Page subtitle "Triage user-submitted feedback and bug reports." then card subtitle "User-submitted feedback, bug reports, and feature requests." — duplicate and inconsistent (which is it: 2 categories or 3?). +- Fix: One subtitle. Drop the card-level one, or merge into "Triage user feedback, bugs, and feature requests." + +### F-21 — `/admin/services` toggle descriptions all end with "system-wide." (4 lines, repetitive) + +- Severity: HIGH · Type: copy-redundant · Effort: S · Route: /admin/services +- Issue: "Allow Telegram notifications system-wide." / "Allow ntfy notifications system-wide." / "Allow browser push notifications system-wide." / "Allow API endpoints and API tokens system-wide." — the suffix adds nothing; the section-level subtitle already says "system-wide". +- Fix: Drop the per-row "system-wide" suffix. + +### F-22 — `/admin/services` subtitle "Enable or disable notification channels system-wide" misses the API toggle + +- Severity: HIGH · Type: copy-incorrect · Effort: S · Route: /admin/services +- Issue: Page subtitle says "notification channels" but the API toggle (which gates external ingest) lives on the same screen. +- Fix: "Enable or disable services system-wide." or split API onto its own card. + +### F-23 — Mood / Achievements content uses English strings even when DE active (e.g. mood entries display "Bad" / "Okay" / "Terrible") + +- Severity: HIGH · Type: copy-missing · Effort: S · Route: /mood +- File: `src/components/mood/*` +- Issue: Mood-score labels not translated to DE (showing "Bad", "Okay", "Terrible" in DE UI). Achievement subtitle awkward in both bundles. +- Fix: Translate the 5 mood enum labels; rewrite achievement subtitle for both bundles. + +### F-24 — `/admin/integrations` toggle row duplicates the card title (Umami / Umami, GlitchTip / GlitchTip) + +- Severity: HIGH · Type: copy-redundant · Effort: S · Route: /admin/integrations +- Issue: Each card has section header "Umami" + a single toggle whose label is also "Umami" with description "Enable Umami tracking." — two of the three lines say the same thing. +- Fix: Drop the inner toggle label or replace with "Enabled" / "Disabled". + +### F-25 — `/admin/integrations` GlitchTip casing inconsistent (header "GlitchTip" vs field labels "Glitchtip DSN" / "Glitchtip environment") + +- Severity: HIGH · Type: typography-inconsistent · Effort: S · Route: /admin/integrations +- Issue: Brand is GlitchTip (capital T). Form labels demote the T. +- Fix: Replace all "Glitchtip" → "GlitchTip" in `messages/*.json` for that section. + +### F-26 — Insights regenerate banner / refresh button conflict (page-level + card-level) + +- Severity: HIGH · Type: copy-redundant · Effort: S · Route: /insights +- Issue: "Regenerate" button top-right, then yellow banner with "Start analysis" CTA (see F-06), then per-tile refresh icons. Three competing refresh entry points. +- Fix: Already partly in A3 scope — one page-level Regenerate, no per-tile spinners. + +### F-27 — Achievement subtitle "Unlock achievements from feature rollout onward through consistent health trends and medication habits." is awkward + +- Severity: HIGH · Type: copy-incorrect · Effort: S · Route: /achievements +- File: `messages/en.json:1975`, `messages/de.json:1975` +- Issue: "From feature rollout onward" is dev-speak; "through consistent health trends and medication habits" is overlong. +- Fix: "Earn achievements by tracking your health and taking your meds on time." / "Sammle Erfolge, indem du regelmäßig misst und deine Medikamente nimmst." + +### F-28 — Achievement title "On-time 1d" / "BMI green · 1d" plus body "for 1 consecutive days" (grammar bug) + +- Severity: HIGH · Type: copy-incorrect (grammar) · Effort: S · Route: /achievements +- File: `messages/en.json` (multiple) / `messages/de.json` (multiple) +- Issue: "Take all medications on time for 1 consecutive days." / "Reach at least 80% 30-day adherence for 1 consecutive days." — singular/plural bug; should be "for 1 day" or "1 consecutive day". +- Fix: Use ICU plural in the description ("for {count, plural, one {1 day} other {# consecutive days}}"). + +### F-29 — `/admin/ai-quality` subtitle uses jargon "Helpful-rate per (severity x provider)" + +- Severity: HIGH · Type: copy-incorrect · Effort: S · Route: /admin/ai-quality +- Issue: `(severity x provider)` is mathy and `x` is a literal letter not a multiplication sign — admin-confusing. +- Fix: "Helpfulness scores grouped by severity and provider, from the last 30 days of user feedback." + +### F-30 — Admin "Inbox zero" empty-state copy assumes English idiom + missing translation + +- Severity: HIGH · Type: copy-incorrect · Effort: S · Route: /admin/feedback +- Issue: Empty state title is the English idiom "Inbox zero" — DE doesn't have an equivalent expression and it's fragile copy. +- Fix: Use a literal title — "All caught up" / "Alles erledigt" or "No open items" / "Keine offenen Einträge". + +### F-31 — `/admin` Recent activity row "Unknown" + "Failed" — no link to the failure + +- Severity: HIGH · Type: copy-missing · Effort: S · Route: /admin +- Issue: Failed login row shows actor "Unknown" + status "Failed" but no link / IP / reason. To investigate, admin has to open Login Overview manually and re-find it. +- Fix: Make the row a link to `/admin/login-overview?focus=`, surface IP at least. + +--- + +## MEDIUM findings + +### F-32 — Settings/Dashboard "Compare to" + 3 nested explanatory texts + +- Severity: MED · Type: copy-redundant · Effort: S · Route: /settings/dashboard +- Issue: Page subtitle "Tile layout and order." + card help "Choose which cards appear on your dashboard and in what order. Defaults work out of the box." + "Adds a dimmed prior-period overlay to every chart and a delta callout to every tile." Three help blocks for one screen. +- Fix: Drop the card help; let the section title and per-control hints speak. + +### F-33 — Mood page subtitle "Mood tracker & entries" — ampersand mid-sentence + redundant words + +- Severity: MED · Type: copy-redundant · Effort: S · Route: /mood +- Issue: "&" is informal. "Mood tracker" and "entries" overlap in meaning. +- Fix: "Your mood entries over time." / "Deine Stimmung im Verlauf." + +### F-34 — Measurements subtitle uses "&" inconsistently with rest of app + +- Severity: MED · Type: typography-inconsistent · Effort: S · Route: /measurements +- Issue: "Weight, blood pressure, pulse & more" — `&` appears here and on Mood; nowhere else. +- Fix: Replace "&" with "and"/"und" everywhere except inline tags. + +### F-35 — Send-test button label inconsistency: "Send test" vs "Test message" + +- Severity: MED · Type: copy-redundant · Effort: S · Route: /settings/notifications +- Issue: Channel reliability uses "Send test", Telegram form footer uses "Test message". Same action, two labels. +- Fix: Standardize on "Send test". + +### F-36 — Status word inconsistency across Admin/System Status: Connected / Running / Active / Configured + +- Severity: MED · Type: typography-inconsistent · Effort: M · Route: /admin/system-status, /settings/integrations, /settings/notifications +- Issue: Multiple status verbs for similar "this is healthy" state. +- Fix: Reduce to two words: "Healthy" (works) / "Disabled" (off) / "Error" (broken). + +### F-37 — Form-field labels inconsistent on trailing colon + +- Severity: MED · Type: typography-inconsistent · Effort: M · Routes: every settings page +- Issue: "Username:", "Email address:", "Gender:", "Height (cm):" carry trailing colon. "Default language", "Compare to" do not. Some pages mix both. +- Fix: Pick one (modern: drop the colon). Sweep all input labels. + +### F-38 — Toggle pseudo-label "Auto:" with trailing colon on /settings/thresholds + +- Severity: MED · Type: typography-inconsistent · Effort: S · Route: /settings/thresholds +- Issue: "Auto:" sits next to a switch — the colon is unusual when the value follows visually below or to the right; other toggles in app omit the colon. +- Fix: Drop the colon, use just "Auto". + +### F-39 — Settings/Dashboard column heads "TILE" / "CHART" in uppercase + +- Severity: MED · Type: typography-inconsistent · Effort: S · Route: /settings/dashboard +- Issue: All-caps column heads only here; everywhere else (Backups, Users, etc.) uses sentence case. +- Fix: "Tile" / "Chart". + +### F-40 — Compliance bars on medication card use uniform purple regardless of % value + +- Severity: MED · Type: typography-inconsistent · Effort: S · Route: /medications, dashboard medication tile +- Issue: 57 % and 58 % both fill purple; no visual cue that this is below "On target". User cannot scan health at a glance. +- Fix: Threshold-coloured fill (≥80 % green, 50–80 % yellow, <50 % red). + +### F-41 — Targets page label inconsistency ("Normal", "Optimal", "Moderate", "On target") + +- Severity: MED · Type: copy-incorrect · Effort: S · Route: /targets +- Issue: 4 different status words across cards. Already partly captured by A7 ("Low / On Target / Stable / Moderate" → DE). +- Fix: Standardize to a small set: "Optimal" / "On target" / "Watch" / "Off target" — and translate. + +### F-42 — `/auth/login` page mobile vertical centering and missing app description + +- Severity: MED · Type: mobile-only · Effort: S · Route: /auth/login (mobile) +- Issue: Login card sits in the lower half of the screen with empty space above. No "Sign in to HealthLog" subtitle, no link to docs/landing. +- Fix: `justify-center` on the wrapper; add a one-line subtitle "Sign in to your HealthLog account." + +### F-43 — `/admin/api-tokens` retains "Collapse" / "Einklappen" button on a single-section page + +- Severity: MED · Type: copy-redundant · Effort: S · Route: /admin/api-tokens, /admin/login-overview +- Issue: Already in Marc's A7 list, but also appears on /admin/login-overview. Both screens have only 1 card so collapse is meaningless. +- Fix: Drop the collapse control on single-card pages. + +### F-44 — `/admin/app-logs` references env var `LOKI_ENDPOINT` to end-users + +- Severity: MED · Type: copy-incorrect · Effort: S · Route: /admin/app-logs +- Issue: Help text "Showing app-process logs only — worker-process logs go to Loki when LOKI_ENDPOINT is configured." +- Fix: "Worker logs are forwarded to Loki when configured." (drop the env var name, link to Operations docs). + +### F-45 — Admin `/admin/users` "Created" date format US, table header "Users" left column + +- Severity: MED · Type: copy-incorrect · Effort: S · Route: /admin/users +- Issue: First column header is "Users" (the user-name column), confusing because the section is titled "User Management". Created column shows `02/20/2026`. +- Fix: Header → "Name" / "User"; date → locale-aware format. + +### F-46 — `/admin/backups` upload help "JSON file matching the current backup schema. Max 10 MB." is grammatically a fragment + +- Severity: MED · Type: copy-incorrect · Effort: S · Route: /admin/backups +- Issue: Sentence has no verb. +- Fix: "Upload a JSON file matching the current backup schema (max 10 MB)." + +### F-47 — `/notifications` (per-event) and `/settings/notifications` (channels) share the same H1 "Notifications" + +- Severity: MED · Type: copy-redundant · Effort: S · Routes: /notifications, /settings/notifications +- Issue: Two pages with identical title; users get confused which one to open. +- Fix: Rename `/notifications` → "Notification rules" and `/settings/notifications` → "Notification channels". + +### F-48 — Insights "AI Health Analysis" + "Personal advisor" subtitle + bottom raw template leak (already in A3) leave little signal + +- Severity: MED · Type: copy-redundant · Effort: S · Route: /insights +- Issue: Card title "AI Health Analysis" + subtitle "Personal advisor" — both labels for the same thing. (Bottom `metric: blood_pressure_sweet` leak already in A3.) +- Fix: Drop "Personal advisor" subtitle. + +### F-49 — Decimal separator inconsistent (88.6 kg vs 88,6 kg) on DE locale + +- Severity: MED · Type: copy-incorrect · Effort: M · Route: dashboard tiles, /targets, /measurements +- Issue: User on DE locale should see "88,6 kg" but the dashboard tile shows "88.2 kg" with a period. Confirms `useFormatters().number()` not used everywhere. +- Fix: Replace `value.toFixed(1)` patterns with `fmt.number()` or `Intl.NumberFormat(locale).format()`. + +### F-50 — German `Bitte erneut versuchen` (impersonal infinitive) vs `Bitte versuche es erneut` (Du-imperative) inconsistent + +- Severity: MED · Type: copy-redundant · Effort: S · Route: many error toasts +- File: `messages/de.json:746, 937–940, 1172, 1222` +- Issue: 6 instances use the infinitive "Bitte erneut versuchen", 1 uses the personal "Bitte versuche es erneut" — DE convention everywhere else in HealthLog is the personal "Du" form. +- Fix: Convert all 6 to "Bitte versuche es erneut.". + +### F-51 — Settings/account "Date of birth" subtext "Used for automatic blood pressure target calculations" — works, but `Gender` says "Used for gender-specific target values" — inconsistent phrasing + +- Severity: MED · Type: copy-redundant · Effort: S · Route: /settings/account +- Issue: Same idea ("we use this to compute targets"), two phrasings in the same card. +- Fix: Pick one — "Used to compute personalized targets." + +### F-52 — Achievements name "Two hundred weigh-ins" spells out the number (200), unlike sibling badges + +- Severity: MED · Type: typography-inconsistent · Effort: S · Route: /achievements +- File: `messages/*.json` achievement titles +- Issue: Most badges use numerals ("80 % adherence", "300 intakes") but this one spells "Two hundred". Inconsistent. +- Fix: "200 weigh-ins" / "200 Wägungen". + +### F-53 — `/settings/ai` AI section subtitle "Provider, model, key." is a comma-separated noun list, not a sentence + +- Severity: MED · Type: copy-incorrect · Effort: S · Route: /settings/ai +- Issue: Awkward. Other settings sections use full sentences. +- Fix: "Configure your AI provider and credentials." + +### F-54 — `/settings/ai` mixes "Codex", "ChatGPT", "OpenAI" without explanation + +- Severity: MED · Type: copy-incorrect · Effort: S · Route: /settings/ai +- Issue: Card title "AI Insights (OpenAI)" but header tag "ChatGPT connected" but provider option "ChatGPT account (Codex)" but env var `CODEX_MODEL`. Three brand names for one feature. +- Fix: Pick one user-facing brand ("ChatGPT") and consistently. Mention Codex only in the dev help under the Disconnect button. + +### F-55 — `/settings/export` "Configure & generate" uses "&" inside a primary CTA + +- Severity: MED · Type: typography-inconsistent · Effort: S · Route: /settings/export +- Issue: Action labels elsewhere avoid `&`. +- Fix: "Configure and generate" or "Configure report". + +### F-56 — `/settings/advanced` subtitle says "danger zone" but card title is "Delete All Data" + +- Severity: MED · Type: copy-redundant · Effort: S · Route: /settings/advanced +- Issue: Subtitle "Account-wide danger zone — irreversibly wipe every health record while keeping your account." paired with card "Delete All Data" — terms shift mid-page. Also overlap with admin "Danger Zone" creates risk of confusion. +- Fix: Rename section header "Reset account data" / "Daten zurücksetzen" — keep wording reserved for the global admin Danger Zone. + +### F-57 — Mobile settings tabs strip clips ("Notificat...") with no scroll affordance + +- Severity: MED · Type: mobile-only · Effort: M · Route: /settings/\* (mobile tab strip) +- Issue: Horizontal pill strip clips the next tab name. No fade/gradient or arrow indicating scrollability. +- Fix: Add right-edge gradient mask + a tiny chevron, or use icon-only tabs on mobile. + +### F-58 — `/admin/reminders` "Run reminder check" button uses wave/chart icon instead of clock/refresh + +- Severity: MED · Type: icon-mismatch · Effort: S · Route: /admin/reminders +- Issue: Icon doesn't match action. +- Fix: Use a `RotateCw` / `Clock` icon for "Run reminder check". + +### F-59 — Mood entries show numeric score `2 (Bad)` / `3 (Okay)` — paren label feels debug-y + +- Severity: MED · Type: typography-inconsistent · Effort: S · Route: /mood +- Issue: Score and label both shown in the same cell. Could be clearer with score badge + label. +- Fix: Pill + descriptor: e.g., a colored 1-5 badge then "Bad" beside it. + +### F-60 — Comment column shows `-` (hyphen) instead of em-dash `—` or empty + +- Severity: MED · Type: typography-inconsistent · Effort: S · Route: /measurements, /mood +- Issue: Inconsistent with em-dash usage elsewhere; also unclear visually. +- Fix: Use empty cell, or consistent em-dash placeholder. + +### F-61 — Empty `IP` and `Location` columns on /admin/login-overview always show `—` + +- Severity: MED · Type: copy-redundant · Effort: M · Route: /admin/login-overview +- Issue: Always `—` because data isn't captured. Wastes table width. +- Fix: Hide columns until populated, OR populate. + +### F-62 — Mobile dashboard greeting "Hello Marc, welcome back." renders English even though user has DE locale (after locale switch) + +- Severity: MED · Type: copy-incorrect · Effort: S · Route: / (dashboard) +- File: `messages/en.json:217-218`, `messages/de.json:217-218` +- Issue: Verify both bundles — current screen showed English; needs spot-check on DE locale. +- Fix: Confirm DE message present and rendered. + +--- + +## LOW findings + +### F-63 — `/auth/login` divider "or" is bare — could read "or use" + +- Severity: LOW · Type: copy-redundant · Effort: S · Route: /auth/login +- Fix: Keep as-is or "Or sign in with password". + +### F-64 — Achievement card "Idiot" badge displays a sparkle icon — same icon as "Lazy Boy" + +- Severity: LOW · Type: icon-mismatch · Effort: S · Route: /achievements +- Fix: Choose distinct icons per badge category. + +### F-65 — Targets page card-icons use mixed colors (purple scale, pink heart, green pulse) — random palette + +- Severity: LOW · Type: typography-inconsistent · Effort: S · Route: /targets +- Fix: Pick a single muted icon palette (Dracula tokens). + +### F-66 — Withings card "Save credentials" button stays grey/muted; user doesn't know it's clickable + +- Severity: LOW · Type: typography-inconsistent · Effort: S · Route: /settings/integrations +- Fix: Disabled until dirty, primary purple when dirty. + +### F-67 — Sort indicators inconsistent on `/measurements` table headers (some show ↕, Date shows only ↓) + +- Severity: LOW · Type: typography-inconsistent · Effort: S · Route: /measurements +- Fix: Always render a single arrow that flips on toggle; muted neutral when inactive. + +### F-68 — `/medications` button "Skipped" is visually almost as prominent as "Taken" + +- Severity: LOW · Type: typography-inconsistent · Effort: S · Route: /medications +- Issue: Skipped is a fallback action; equal-weight buttons can lead to mis-taps. +- Fix: Demote Skipped to ghost / secondary; keep Taken as primary. + +### F-69 — Achievements card "Next goal" reads `Progress to unlock: 166 / 200 (83%)` then "320 points" on a separate line + +- Severity: LOW · Type: typography-inconsistent · Effort: S · Route: /achievements +- Fix: Inline: "166/200 (83%) · 320 Punkte". + +### F-70 — `/settings/integrations` Withings client-id placeholder "Saved — enter new to repla…" gets truncated mid-word on desktop 1280 + +- Severity: LOW · Type: copy-incorrect · Effort: S · Route: /settings/integrations +- Fix: Use the `placeholder` attribute properly, or a shorter "Already saved". + +### F-71 — `/insights` "Generated 57 minutes ago" relative time, but other places show absolute timestamps + +- Severity: LOW · Type: typography-inconsistent · Effort: S · Route: /insights +- Fix: Pick one convention (relative for recent, absolute on hover) consistently. + +### F-72 — 404 page on /settings/profile and /admin/zielwerte etc. is bare ("404 | This page could not be found.") — no nav + +- Severity: LOW · Type: copy-missing · Effort: S · Route: any 404 +- Issue: This is the Next.js default. No "Back to dashboard" link. +- Fix: Custom 404 with logo + "Back to dashboard" CTA. + +### F-73 — Onboarding subtitle "Three quick steps. You can finish anything later from Settings." duplicated by footer "Skip this step — you can finish setup later from Settings." + +- Severity: LOW · Type: copy-redundant · Effort: S · Route: /onboarding +- Fix: Drop one of the two messages. + +### F-74 — `/settings/account` "Used for automatic blood pressure target calculations." appears under date-of-birth, but blood-pressure target also uses gender — split incorrectly + +- Severity: LOW · Type: copy-incorrect · Effort: S · Route: /settings/account +- Fix: Hoist the explanation to the section level: "Birth date and gender personalize your targets." + +### F-75 — `/insights` "Based on your last 90 days" uses absolute "90" but range tabs let user pick 7/30/90/All — banner doesn't update + +- Severity: LOW · Type: copy-incorrect · Effort: M · Route: /insights +- Fix: Tie the message to the active range. + +### F-76 — `/admin/system-status` cards "Last Reminder Check" use inconsistent capitalization + +- Severity: LOW · Type: typography-inconsistent · Effort: S · Route: /admin/system-status +- Fix: Sentence case throughout: "Last reminder check". + +### F-77 — `/admin/api-tokens` "Last used: Never" / "Created: 05/05/2026" — colon style + date format US (mobile) + +- Severity: LOW · Type: typography-inconsistent · Effort: S · Route: /admin/api-tokens (mobile) +- Already covered by F-04 and F-37. + +### F-78 — Settings/Account "Profile" sub-card heading repeats the page-level "Account" framing (mini-version of F-08) + +- Severity: LOW · Type: copy-redundant · Effort: S · Route: /settings/account +- Fix: Drop the inner "Profile" heading. + +--- + +## Top-10 (by impact) — Wave-B priority + +1. **F-01** ⭐ — Hardcoded "bis ... Uhr" in `formatTimeWindowRange` (CRIT, S) +2. **F-02** ⭐ — Login Overview shows insights events (CRIT, S) +3. **F-03** ⭐ — "Idiot" / "Lazy Boy" achievements (CRIT, S) +4. **F-05** ⭐ — Admin "Pick a section from the sidebar" wrong on mobile (CRIT, S) +5. **F-04** ⭐ — Date format US-leaks across the app (CRIT, M) +6. **F-09** ⭐ — `WEEKLY_AUTO` raw enum (HIGH, S) +7. **F-10** ⭐ — `WITHINGS` raw uppercase (HIGH, S) +8. **F-11** ⭐ — User role-change `→ User` / `→ Admin` no label (HIGH, S) +9. **F-07** ⭐ — Personal targets / thresholds three different names (HIGH, S) +10. **F-08** ⭐ — Page header / card header duplicate across many admin/settings pages (HIGH, S) + +--- + +## Methodology + +- Production captured at https://healthlog.bombeck.io with Marc's session cookie at Pixel 5 (mobile) and 1280×900 (desktop) viewports — 30+ routes per viewport. +- All visible strings cross-checked against `messages/en.json` (2474 lines) and `messages/de.json` (2474 lines). +- Code grep against `src/components/admin/`, `src/components/settings/`, `src/components/measurements/`, `src/components/medications/`, `src/components/mood/`, `src/lib/time-window-format.ts`. +- Settings real route slugs: account, integrations, notifications, dashboard, thresholds, ai, api, export, advanced, about (note: `/settings/profile`, `/settings/audit-log`, `/settings/zielwerte` are 404 — referenced in CLAUDE.md but don't exist). +- Admin real route slugs: system-status, general, services, integrations, ai-quality, feedback, reminders, users, api-tokens, login-overview, app-logs, backups, danger-zone (note: `/admin/zielwerte`, `/admin/audit-log` are 404). + +78 findings (0 dropped during review). diff --git a/.planning/phase-B-quality-report.md b/.planning/phase-B-quality-report.md new file mode 100644 index 000000000..17857f2fa --- /dev/null +++ b/.planning/phase-B-quality-report.md @@ -0,0 +1,82 @@ +# Phase B — Apply A8 quality findings (v1.4.19) + +Wave B follow-on to the A8 write-only audit. Pragmatic triage of +the 78 findings: ship every CRITICAL + every low-risk HIGH inline, +defer the rest to v1.4.20. + +## CRITICAL — 6 / 6 fixed + +| Finding | Status | Commit | Notes | +| ------- | ------ | ------ | ----- | +| F-01 `formatTimeWindowRange` mixed-language `bis ... Uhr` | fixed | `180f46c` | Locale param threaded through medication card + form. TDD: 6 cases. | +| F-02 `/admin/login-overview` shows insights events | fixed | `dc75fe8` | Section now sets `filter=auth`; action dropdown filtered to `auth.*`. TDD: URL assertion. | +| F-03 Achievements `Idiot` / `Lazy Boy` titles | fixed | `7a6bc81` | Renamed in EN + DE; banned-word regression suite. | +| F-04 Date inputs render mm/dd/yyyy on DE | fixed | `ff6e184` | `lang={locale}` on every native `` in the app. | +| F-05 Admin "Pick a section from the sidebar" wrong on mobile | fixed | `4feacad` | Dropped sidebar reference in EN + DE welcome subtitle. | +| F-06 Insights legacy CTA verb mismatch | fixed | `d263125` | Legacy-payload button now says "Regenerate" in EN/DE; empty-state "Start analysis" untouched. | + +## HIGH — 21 fixed / 4 deferred (target: 15) + +| Finding | Status | Commit / reason | +| ------- | ------ | --------------- | +| F-07 thresholds three names | fixed | `1f4d6a2` | +| F-08 page+card title duplicate | fixed (partial) | `70ebe32` (danger-zone, feedback). Other affected pages deferred. | +| F-09 backup type raw enum | fixed | `1ae9b9d` | +| F-10 `WITHINGS` rendered ALL CAPS | fixed | `1ae9b9d` | +| F-11 `→ User` / `→ Admin` no label | fixed | `42b0f11` | +| F-12 mobile user cards no aria-labels | deferred | spot-check showed every icon button already has `aria-label` + `title`. *not-an-issue*; see backlog. | +| F-13 mobile sys/dia same heart icon | fixed | `876e074` | +| F-14 settings/integrations status duplicated | deferred | already shipped in v1.4.19 A5. | +| F-15 ntfy in subtitle, missing on board | fixed | `3f6e670` | +| F-16 "Configured" + "Enabled" double badge | fixed | `876e074` | +| F-17 raw `auth.token.autoissue.native` event-key | fixed | `bc81fd7` | +| F-18 ISO timestamp in token name | fixed | `713b494` | +| F-19 raw `*` permission badge | fixed | `3f6e670` | +| F-20 admin/feedback dupe subtitle | fixed | `3f6e670` | +| F-21 "system-wide" suffix repeated 4× | fixed | `3f6e670` | +| F-22 services subtitle misses API toggle | fixed | `3f6e670` | +| F-23 mood/achievements DE strings | deferred | spot-check showed every label already translated. *not-an-issue*; see backlog. | +| F-24 admin/integrations duplicate toggle label | fixed | `98cd13c` | +| F-25 `Glitchtip` casing | fixed | `3f6e670` | +| F-26 insights regenerate banner+button conflict | deferred | partial overlap with A3 + F-06; per-tile spinner removal owns by v1.4.20 Insights redesign. | +| F-27 awkward achievements subtitle | fixed | `3f6e670` | +| F-28 plural bug "for 1 consecutive days" | fixed | `98cd13c` | +| F-29 jargon "(severity x provider)" | fixed | `3f6e670` | +| F-30 "Inbox zero" empty-state idiom | fixed | `3f6e670` | +| F-31 audit-preview row no link | fixed | `bc81fd7` | + +## MED + LOW — 31 + 16 deferred to v1.4.20 + +Severity-grouped list with file:line in +`.planning/v1420-backlog.md`. Highlights: F-36 status-word +taxonomy, F-49 decimal-separator sweep, F-37/F-38 trailing-colon +form-label sweep — all small but cross-cutting. Ideal pre-work +for the v1.4.20 Insights redesign. + +## Final verification + +- `pnpm typecheck` — clean. +- `pnpm lint` — 12 warnings, 0 errors. All warnings pre-existing + baseline (`_request` / `_params` / `err`); zero new from B. +- `pnpm format:check` — 8 files dirty. All `.planning/*` / + `docs/audit/*` baseline noise pre-existing on `origin/main` + (verified via `git log` + `git show`); zero source files + dirty after the commit `ae2f671` prettier sweep. +- `pnpm test --run` — 1669 / 1669 green. Up from 1658 + baseline; +11 from new TDD cases (`time-window-format` 6, + `achievements-no-insults` 4, `login-overview-auth-filter` 1). +- `pnpm test:integration` — 67 / 67 green. + +## Pointers + +- v1.4.20 backlog: `.planning/v1420-backlog.md` +- A8 raw findings: `.planning/phase-A8-quality-findings.md` +- Wave A reports: `.planning/phase-A{1,2,3,4,5,6,7}-report.md` + +## Commits landed on origin/main + +`180f46c`, `dc75fe8`, `7a6bc81`, `ff6e184`, `4feacad`, `d263125`, +`1ae9b9d`, `42b0f11`, `1f4d6a2`, `3f6e670`, `70ebe32`, `98cd13c`, +`876e074`, `bc81fd7`, `713b494`, `ae2f671` — 16 atomic commits. +No `--no-verify`, no `--no-gpg-sign`. Pre-commit hooks green +on every commit. diff --git a/.planning/v1420-backlog.md b/.planning/v1420-backlog.md new file mode 100644 index 000000000..110c668e7 --- /dev/null +++ b/.planning/v1420-backlog.md @@ -0,0 +1,214 @@ +# v1.4.20 backlog — A8 deferred MED + LOW + +Source: `.planning/phase-A8-quality-findings.md` (78 findings). +v1.4.19 Wave B applied 6/6 CRIT and 21/25 HIGH; this file is +the carry-over. + +The strategic Insights redesign + AI Coach is the v1.4.20 +headline. Pick winners from the list below before / after that +work — most of these are 1–3 lines of i18n or a single +component edit. + +## HIGH deferred (4) + +- **F-12** — User-management mobile cards, 5 icon buttons + (`src/components/admin/user-management-section.tsx`). Spot + check during Wave B showed every icon button already carries + `aria-label` + `title` (edit, reset password, force logout, + role change). Treat as *not-an-issue*; revisit only if a + concrete a11y audit re-flags it. +- **F-14** — Settings/Integrations status duplicated. Already + shipped under v1.4.19 A5; keep an eye out for regressions but + no new work here. +- **F-23** — Mood / Achievements DE strings. Spot check showed + every mood label (`mood.levelLausig` … `mood.moodLabel5`, + `mood.moodAwful` … `mood.moodGreat`) is translated in + `messages/de.json`. The achievement-subtitle awkwardness was + fixed by F-27. Treat as *not-an-issue*; revisit only with a + fresh DE-locale screen-by-screen pass. +- **F-26** — Insights regenerate banner / refresh button conflict. + Already partially in A3 scope (page-level Regenerate, + per-recommendation Regenerate). The banner button copy was + fixed in F-06. Per-tile refresh icons removal is a v1.4.20 + Insights-redesign concern. + +## MEDIUM (31) + +### Layout / spacing + +- **F-32** — `/settings/dashboard` triple help blocks + (subtitle + card help + "Adds a dimmed prior-period overlay…"). + `messages/*.json:settings.sections.dashboard.description` + + `dashboardLayout.compareCallout`. **S**. +- **F-39** — `/settings/dashboard` column heads `TILE` / `CHART` + in uppercase (`src/components/settings/dashboard-layout-section.tsx`). + Sentence-case to match the rest of the app. **S**. +- **F-57** — Mobile settings tab strip clips `Notificat…` with + no scroll affordance (`src/app/settings/[section]/page.tsx`, + the inline pill strip). Add a right-edge fade gradient or a + chevron. **M**. + +### Copy nits + +- **F-33** — `/mood` subtitle "Mood tracker & entries" — drop + ampersand + redundant words. + `messages/*.json:mood.subtitle`. **S**. +- **F-34** — `/measurements` subtitle "Weight, blood pressure, + pulse & more" — replace `&` with `and` / `und`. + `messages/*.json:measurements.subtitle`. **S**. +- **F-35** — Send-test button label "Send test" vs "Test + message". `messages/*.json:settings.telegramTest`, + `settings.notificationStatus.sendTest`. **S**. +- **F-36** — Status-word inconsistency "Connected / Running / + Active / Configured" across system-status / integrations / + notifications. Reduce to two words: `Healthy` / `Disabled` / + `Error`. **M**. +- **F-37** — Form labels with trailing colon ("Username:" vs + "Default language") — sweep across every settings page, + pick the no-colon variant. **M**. +- **F-38** — `/settings/thresholds` "Auto:" pseudo-label + trailing colon. `src/components/settings/thresholds-editor-section.tsx`, + `messages/*.json:thresholds.autoModeLabel`. **S**. +- **F-44** — `/admin/app-logs` references env var `LOKI_ENDPOINT` + to end-users. `messages/*.json:admin.appLogs.workerHint`. **S**. +- **F-45** — `/admin/users` first column header is "Users" + (the user-name column) — should be "Name" or "User". + `src/components/admin/user-management-section.tsx` table head. + Date format already covered by F-04. **S**. +- **F-46** — `/admin/backups` upload help "JSON file matching + the current backup schema. Max 10 MB." is a sentence fragment. + `messages/*.json:admin.section.backups.uploadHelp`. **S**. +- **F-47** — `/notifications` (per-event rules) and + `/settings/notifications` (channels) share H1 "Notifications". + Rename one to "Notification rules", the other to + "Notification channels". **S**. +- **F-48** — `/insights` "AI Health Analysis" + "Personal advisor" + subtitle redundant. Drop the "Personal advisor" subtitle — + but coordinate with v1.4.20 Insights redesign first. **S**. +- **F-49** — Decimal separator inconsistent on DE locale + (`88.6 kg` vs `88,6 kg`). Sweep `value.toFixed(1)` patterns, + replace with `fmt.number()` / + `Intl.NumberFormat(locale).format()`. **M**. +- **F-50** — DE error-toast tone: 6 instances of + `Bitte erneut versuchen` (impersonal infinitive) vs the + HealthLog-standard personal `Bitte versuche es erneut`. + `messages/de.json:746, 937–940, 1172, 1222`. **S**. +- **F-51** — `/settings/account` DOB hint "Used for automatic + blood pressure target calculations" vs Gender hint "Used for + gender-specific target values" — pick one phrasing. + `messages/*.json:settings.dateOfBirthHint`, + `settings.genderHint`. **S**. +- **F-53** — `/settings/ai` subtitle "Provider, model, key." is + a noun list, not a sentence. `messages/*.json:settings.sections.ai.description`. **S**. +- **F-54** — `/settings/ai` mixes "Codex", "ChatGPT", "OpenAI" — + pick one user-facing brand. **S** (i18n) but with + cross-component consequences. +- **F-55** — `/settings/export` "Configure & generate" CTA uses + ampersand. `messages/*.json:settings.sections.export.cta`. **S**. +- **F-56** — `/settings/advanced` subtitle says "danger zone" + but card title is "Delete All Data". Rename section header to + "Reset account data" / "Daten zurücksetzen" so the user-side + surface is distinct from admin Danger Zone. + `messages/*.json:settings.sections.advanced.title`. **S**. +- **F-58** — `/admin/reminders` "Run reminder check" button uses + wave/chart icon — change to `RotateCw` / `Clock` icon. + `src/components/admin/reminders-section.tsx`. **S**. +- **F-62** — Mobile dashboard greeting "Hello Marc, welcome + back." in EN vs DE. Spot-check whether DE bundle renders; + fall-back-to-EN when missing was already audited. + `messages/*.json:dashboard.greeting`, + `dashboard.greetingSubtitle`. **S**. + +### Typography / mobile / icons + +- **F-40** — Compliance bars on medication card uniform purple + regardless of % value. Threshold-coloured fill (≥80 % green, + 50–80 % yellow, <50 % red). + `src/components/medications/medication-card.tsx` Progress. **S**. +- **F-41** — Targets page label inconsistency + ("Normal", "Optimal", "Moderate", "On target"). Already + partly covered by A7. Standardise to a small set: + `Optimal` / `On target` / `Watch` / `Off target`. + `src/app/targets/page.tsx`. **S**. +- **F-42** — `/auth/login` mobile vertical centering + missing + app subtitle ("Sign in to your HealthLog account."). **S**. +- **F-43** — `/admin/api-tokens` "Collapse" / "Einklappen" already + removed by A7; F-43 also flags `/admin/login-overview` — + same pattern, single-card page, drop the toggle. + `src/components/admin/login-overview-section.tsx` + `expanded` toggle + `setExpanded`. **S**. +- **F-59** — Mood entries show numeric score `2 (Bad)` / + `3 (Okay)` — paren label feels debug-y. Switch to coloured + 1-5 badge + descriptor. + `src/components/mood/mood-list.tsx`. **S**. +- **F-60** — Comment column shows `-` (hyphen) instead of empty + cell or em-dash. `src/components/measurements/measurement-list.tsx`, + `src/components/mood/mood-list.tsx`. **S**. +- **F-61** — Empty IP / Location columns on /admin/login-overview + always render `—`. Hide columns when no rows have the field + populated. `src/components/admin/login-overview-section.tsx`. **M**. + +## LOW (16) + +- **F-63** — `/auth/login` divider "or" — keep as-is or + "Or sign in with password". **S**. +- **F-64** — Achievement card "Idiot" / "Lazy Boy" badges share + a sparkle icon. (Titles already fixed via F-03; pick distinct + icons next pass.) `src/components/achievements/*`. **S**. +- **F-65** — Targets page card-icons mixed colors (purple scale, + pink heart, green pulse) — pick a single muted Dracula + palette. `src/app/targets/page.tsx`. **S**. +- **F-66** — Withings card "Save credentials" button stays + grey/muted; user doesn't know it's clickable. Make it + `disabled` until dirty, primary purple when dirty. + `src/components/settings/withings-section.tsx`. **S**. +- **F-67** — Sort indicators inconsistent on /measurements + table headers (some `↕`, Date shows `↓`). Render a single + arrow that flips on toggle. + `src/components/measurements/measurement-list.tsx` table + header. **S**. +- **F-68** — `/medications` button "Skipped" visually almost as + prominent as "Taken". Demote to ghost / secondary. + `src/components/medications/medication-card.tsx`. **S**. +- **F-69** — Achievements card "Next goal" inline format — + "Progress to unlock: 166 / 200 (83%)" then "320 points" on a + separate line. Inline both. `src/components/achievements/*`. **S**. +- **F-70** — `/settings/integrations` Withings client-id + placeholder gets truncated mid-word at 1280 px desktop. Use a + shorter "Already saved". + `messages/*.json:settings.withingsCredentialsSavedPlaceholder`. **S**. +- **F-71** — `/insights` "Generated 57 minutes ago" — pick one + convention (relative for recent, absolute on hover). **S**. +- **F-72** — Bare Next.js 404 ("This page could not be found.") + with no nav. Add custom `app/not-found.tsx` with logo + + "Back to dashboard" CTA. **S**. +- **F-73** — Onboarding subtitle + footer say the same thing + ("Skip this step — you can finish setup later from + Settings."). Drop one. `src/app/onboarding/page.tsx`. **S**. +- **F-74** — `/settings/account` DOB hint mentions only blood + pressure but BP target also uses gender — hoist explanation + to section level. Already overlaps with F-51. **S**. +- **F-75** — `/insights` "Based on your last 90 days" doesn't + update with the active range tab. Tie the message to the + selected range. + `src/components/insights/insight-advisor-card.tsx`. **M**. +- **F-76** — `/admin/system-status` cards "Last Reminder Check" + inconsistent capitalisation. Sentence-case throughout. + `messages/*.json:admin.section.system-status.*`. **S**. +- **F-77** — `/admin/api-tokens` "Last used: Never" / + "Created: 05/05/2026" colon style + date format. Already + covered by F-04 (date format) and F-37 (colon style). **S**. +- **F-78** — `/settings/account` "Profile" sub-card heading + repeats page-level "Account" framing — drop the inner + heading. `src/components/settings/account-section.tsx`. **S**. + +## Notes for v1.4.20 picks + +- The MED block is dominated by copy nits — running prettier + + a single i18n sweep would land 12+ of them in one commit. +- F-36 (status-word taxonomy) is a small refactor that pays + off everywhere; consider doing it before the Insights + redesign so the new surface inherits the canonical + vocabulary. +- F-49 (decimal separator) wants a codemod or grep sweep, + not hand-edits — touches many tile components.