From 562ff8a277feafb1f149dbbb5c18e48efc6c08b9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andr=C3=A9=20Bombeck?= Date: Thu, 22 Oct 2026 20:25:48 +0200 Subject: [PATCH] fix(a11y): name every control by the text it shows axe 4.14 checks WCAG 2.5.3 (label in name), and the app frame failed it on every page: the account link, the user menu and the day strip carried an aria-label that left out their visible text. The same class sat in the coverage meter, the score ring, medication links, instrument cards, filter pills with a value set, cycle calendar days (named by an ISO key), admin user and login rows, a few sort and action buttons, and the French, Spanish, Italian and Polish weekday chips, whose abbreviations were not part of the day names. Controls with visible text are now named by it, with any extra purpose added as screen-reader text; the select pill and calendar days spell out a name that contains what they show. The day and conversation strips keep their action as the tooltip. --- e2e/coach-layout.spec.ts | 6 ++-- e2e/day-view.spec.ts | 4 ++- messages/de.json | 1 - messages/en.json | 1 - messages/es.json | 7 ++--- messages/fr.json | 5 ++-- messages/it.json | 5 ++-- messages/ko.json | 1 - messages/nl.json | 1 - messages/pl.json | 15 +++++----- messages/pt-BR.json | 1 - .../baselines/source-function-lines.json | 8 ++--- .../admin/login-overview-section.tsx | 4 ++- .../admin/user-management-section.tsx | 12 ++------ .../cycle/__tests__/cycle-calendar.test.tsx | 4 +-- src/components/cycle/cycle-calendar.tsx | 29 ++++++++++++++++++- src/components/daily/today-hero.tsx | 8 +++-- .../documents/document-detail-sheet.tsx | 3 +- .../health-score-coverage-axis-guard.test.tsx | 4 ++- src/components/insights/correlation-card.tsx | 4 ++- .../derived/__tests__/coverage-meter.test.tsx | 5 +++- .../insights/derived/coverage-meter.tsx | 4 ++- .../layout/__tests__/shell-dock.test.tsx | 6 ++-- .../layout/__tests__/sidebar-nav.test.tsx | 8 +++-- src/components/layout/shell-dock.tsx | 5 ++-- src/components/layout/sidebar-nav.tsx | 19 +++++------- src/components/layout/top-bar.tsx | 4 +-- .../measurement-list-filter-select.test.tsx | 12 ++++---- .../measurement-list-locked-type.test.tsx | 2 +- .../__tests__/medication-card-header.test.tsx | 7 ++++- .../medications/intake-history-list-v2.tsx | 10 ++++--- .../medications/medication-card-header.tsx | 2 +- .../medications/medication-table.tsx | 2 +- .../mental-health/instrument-card.tsx | 2 +- .../mood-list-filter-select.test.tsx | 6 ++-- src/components/ui/filter-bar.tsx | 14 +++++---- 36 files changed, 134 insertions(+), 97 deletions(-) diff --git a/e2e/coach-layout.spec.ts b/e2e/coach-layout.spec.ts index ae6bfbaec..75c1da37a 100644 --- a/e2e/coach-layout.spec.ts +++ b/e2e/coach-layout.spec.ts @@ -514,10 +514,8 @@ test.describe("Coach page frame", () => { expect(await stripBox(strip(page))).toEqual(stripOpen); await expect(expand(page)).toBeFocused(); await expect(expand(page)).toHaveAttribute("aria-expanded", "false"); - await expect(expand(page)).toHaveAttribute( - "aria-label", - "Show conversations", - ); + // Named by its visible label; the action is the tooltip (WCAG 2.5.3). + await expect(expand(page)).toHaveAttribute("title", "Show conversations"); await expect( panel(page).locator('[data-slot="coach-history-select"]'), ).toHaveCount(0); diff --git a/e2e/day-view.spec.ts b/e2e/day-view.spec.ts index 0aae88187..c4e5f5fd4 100644 --- a/e2e/day-view.spec.ts +++ b/e2e/day-view.spec.ts @@ -192,7 +192,9 @@ test.describe("the day view", () => { await expect(expand).toBeFocused(); await expect(expand).toHaveAttribute("aria-expanded", "false"); await expect(expand).toHaveAttribute("aria-controls", "day-docked-panel"); - expect(await expand.getAttribute("aria-label")).toMatch(/^Show /); + // Named by its visible date (WCAG 2.5.3); the action is the tooltip. + expect(await expand.getAttribute("aria-label")).toBeNull(); + expect(await expand.getAttribute("title")).toMatch(/^Show /); expect((await rail.boundingBox())!.width).toBeLessThanOrEqual(40); // Another page: the edge is still there and brings the same day back, diff --git a/messages/de.json b/messages/de.json index 1b8503088..6c5b6ba81 100644 --- a/messages/de.json +++ b/messages/de.json @@ -11908,7 +11908,6 @@ }, "chat": { "title": "Über dieses Dokument chatten", - "openAria": "Coach über dieses Dokument fragen", "openButton": "Coach fragen" }, "readingConsent": { diff --git a/messages/en.json b/messages/en.json index 07b2f0e74..26878b719 100644 --- a/messages/en.json +++ b/messages/en.json @@ -11960,7 +11960,6 @@ }, "chat": { "title": "Chat about this document", - "openAria": "Ask the Coach about this document", "openButton": "Ask the Coach" }, "readingConsent": { diff --git a/messages/es.json b/messages/es.json index 36448140f..cddc6c79b 100644 --- a/messages/es.json +++ b/messages/es.json @@ -1556,12 +1556,12 @@ "weekdays": { "label": "Días de la semana", "short": { - "mo": "Mo", + "mo": "Lu", "tu": "Ma", "we": "Mi", "th": "Ju", - "fr": "Fr", - "sa": "Sa", + "fr": "Vi", + "sa": "Sá", "su": "Do" }, "long": { @@ -11908,7 +11908,6 @@ }, "chat": { "title": "Chatea sobre este documento", - "openAria": "Pregunta al Coach sobre este documento", "openButton": "Preguntar al Coach" }, "readingConsent": { diff --git a/messages/fr.json b/messages/fr.json index a8236ec64..c11135c11 100644 --- a/messages/fr.json +++ b/messages/fr.json @@ -1556,11 +1556,11 @@ "weekdays": { "label": "Jours de la semaine", "short": { - "mo": "Mo", + "mo": "Lu", "tu": "Ma", "we": "Me", "th": "Je", - "fr": "Fr", + "fr": "Ve", "sa": "Sa", "su": "Di" }, @@ -11881,7 +11881,6 @@ }, "chat": { "title": "Discuter de ce document", - "openAria": "Interroger le Coach sur ce document", "openButton": "Demander au Coach" }, "readingConsent": { diff --git a/messages/it.json b/messages/it.json index 99ee64dd2..c6ad4adf8 100644 --- a/messages/it.json +++ b/messages/it.json @@ -1556,11 +1556,11 @@ "weekdays": { "label": "Giorni della settimana", "short": { - "mo": "Mo", + "mo": "Lu", "tu": "Ma", "we": "Me", "th": "Gi", - "fr": "Fr", + "fr": "Ve", "sa": "Sa", "su": "Do" }, @@ -11908,7 +11908,6 @@ }, "chat": { "title": "Chatta su questo documento", - "openAria": "Chiedi al Coach su questo documento", "openButton": "Chiedi al Coach" }, "readingConsent": { diff --git a/messages/ko.json b/messages/ko.json index 286e9d099..2485ebc17 100644 --- a/messages/ko.json +++ b/messages/ko.json @@ -11841,7 +11841,6 @@ }, "chat": { "title": "이 문서에 대해 대화", - "openAria": "이 문서에 대해 코치에게 묻기", "openButton": "코치에게 묻기" }, "readingConsent": { diff --git a/messages/nl.json b/messages/nl.json index 4a6ae7395..08d578b27 100644 --- a/messages/nl.json +++ b/messages/nl.json @@ -11841,7 +11841,6 @@ }, "chat": { "title": "Chatten over dit document", - "openAria": "De Coach een vraag stellen over dit document", "openButton": "Vraag het de Coach" }, "readingConsent": { diff --git a/messages/pl.json b/messages/pl.json index e82a5da9e..3dee081df 100644 --- a/messages/pl.json +++ b/messages/pl.json @@ -1556,13 +1556,13 @@ "weekdays": { "label": "Dni tygodnia", "short": { - "mo": "Mo", - "tu": "Wt", - "we": "Śr", - "th": "Cz", - "fr": "Fr", - "sa": "Sa", - "su": "Nd" + "mo": "Pon", + "tu": "Wto", + "we": "Śro", + "th": "Czw", + "fr": "Pią", + "sa": "Sob", + "su": "Nie" }, "long": { "mo": "Poniedziałek", @@ -11908,7 +11908,6 @@ }, "chat": { "title": "Porozmawiaj o tym dokumencie", - "openAria": "Zapytaj Coacha o ten dokument", "openButton": "Zapytaj Coacha" }, "readingConsent": { diff --git a/messages/pt-BR.json b/messages/pt-BR.json index 00030a2e0..e3e7ddb9a 100644 --- a/messages/pt-BR.json +++ b/messages/pt-BR.json @@ -11841,7 +11841,6 @@ }, "chat": { "title": "Conversar sobre este documento", - "openAria": "Perguntar ao Coach sobre este documento", "openButton": "Perguntar ao Coach" }, "readingConsent": { diff --git a/src/__tests__/baselines/source-function-lines.json b/src/__tests__/baselines/source-function-lines.json index 6ee015b92..fe8ee6eb1 100644 --- a/src/__tests__/baselines/source-function-lines.json +++ b/src/__tests__/baselines/source-function-lines.json @@ -91,7 +91,7 @@ "components/admin/host-metrics-chart.tsx :: HostMetricsChart": 232, "components/admin/invite-tokens-section.tsx :: InviteTokensSection": 525, "components/admin/key-backup-card.tsx :: KeyBackupCard": 238, - "components/admin/login-overview-section.tsx :: LoginOverviewSection": 520, + "components/admin/login-overview-section.tsx :: LoginOverviewSection": 522, "components/admin/pulse-day-mean-card.tsx :: PulseDayMeanCard": 305, "components/admin/reminders-section.tsx :: RemindersSection": 309, "components/admin/user-management-section.tsx :: UserManagementSection": 634, @@ -111,11 +111,11 @@ "components/custom-metrics/custom-metric-form.tsx :: CustomMetricForm": 214, "components/custom-metrics/custom-metric-history-list.tsx :: CustomMetricHistoryList": 302, "components/cycle/bbt-chart.tsx :: BbtChart": 244, - "components/cycle/cycle-calendar.tsx :: CycleCalendar": 303, + "components/cycle/cycle-calendar.tsx :: CycleCalendar": 304, "components/cycle/cycle-settings.tsx :: CycleSettings": 228, "components/cycle/cycle-view.tsx :: CycleView": 408, "components/cycle/log-day-sheet.tsx :: LogDaySheet": 753, - "components/daily/today-hero.tsx :: TodayHero": 490, + "components/daily/today-hero.tsx :: TodayHero": 492, "components/dashboard/dashboard-header.tsx :: DashboardHeader": 215, "components/dashboard/medication-intake-quick-add.tsx :: MedicationIntakeQuickAdd": 384, "components/day/day-layer.tsx :: DayLayer": 374, @@ -194,7 +194,7 @@ "components/medications/glp1-medication-card.tsx :: Glp1MedicationCard": 405, "components/medications/injection-site-picker.tsx :: InjectionSitePicker": 287, "components/medications/intake-history-editable.tsx :: IntakeHistoryEditable": 224, - "components/medications/intake-history-list-v2.tsx :: IntakeHistoryListV2": 435, + "components/medications/intake-history-list-v2.tsx :: IntakeHistoryListV2": 437, "components/medications/intake-import-dialog.tsx :: IntakeImportDialog": 208, "components/medications/log-intake-dialog.tsx :: LogIntakeDialog": 267, "components/medications/medication-card.tsx :: MedicationCard": 413, diff --git a/src/components/admin/login-overview-section.tsx b/src/components/admin/login-overview-section.tsx index a20d6578d..01cded22c 100644 --- a/src/components/admin/login-overview-section.tsx +++ b/src/components/admin/login-overview-section.tsx @@ -499,10 +499,12 @@ export function LoginOverviewSection() { copyIp(entry.id, entry.ipAddress as string) } title={entry.ipAddress} - aria-label={t("admin.copyIp")} data-slot="login-overview-ip" className="hover:text-foreground focus-visible:ring-ring inline-flex max-w-[10rem] items-center gap-1 rounded font-mono focus-visible:ring-2 focus-visible:outline-none" > + + {t("admin.copyIp")} + {entry.ipAddress} diff --git a/src/components/admin/user-management-section.tsx b/src/components/admin/user-management-section.tsx index 6f8f23a34..5ae5c41fb 100644 --- a/src/components/admin/user-management-section.tsx +++ b/src/components/admin/user-management-section.tsx @@ -202,14 +202,10 @@ export function UserManagementSection() { ? t("admin.demoteToUser") : t("admin.promoteToAdmin") } - aria-label={ - u.role === "ADMIN" - ? t("admin.demoteToUser") - : t("admin.promoteToAdmin") - } >