From 4c3d2530669df29f97be58df5d980876649ee2cc Mon Sep 17 00:00:00 2001 From: Marcel Date: Sat, 28 Mar 2026 18:22:45 +0100 Subject: [PATCH 1/3] test(#147): add axe-core accessibility spec with color-contrast enabled MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Introduces the wcag2a/wcag2aa E2E suite from the test-suite branch with the color-contrast rule active — no disableRules exclusion. Also adds /coverage/ to .prettierignore so generated lcov reports don't fail the lint hook. This commit intentionally fails the axe suite until the contrast fixes land in the next commits. Co-Authored-By: Claude Sonnet 4.6 --- frontend/.prettierignore | 1 + frontend/e2e/accessibility.spec.ts | 58 ++++++++++++++++++++++++++++++ 2 files changed, 59 insertions(+) create mode 100644 frontend/e2e/accessibility.spec.ts diff --git a/frontend/.prettierignore b/frontend/.prettierignore index 2e8d4019..dc7a1d03 100644 --- a/frontend/.prettierignore +++ b/frontend/.prettierignore @@ -16,3 +16,4 @@ bun.lockb # Test artifacts /test-results/ /e2e/.auth/ +/coverage/ diff --git a/frontend/e2e/accessibility.spec.ts b/frontend/e2e/accessibility.spec.ts new file mode 100644 index 00000000..06ac90ab --- /dev/null +++ b/frontend/e2e/accessibility.spec.ts @@ -0,0 +1,58 @@ +import AxeBuilder from '@axe-core/playwright'; +import { test, expect } from '@playwright/test'; + +/** + * Automated accessibility checks using axe-core (wcag2a + wcag2aa). + * Authenticated pages use the stored admin session from playwright.config.ts. + * The login page test overrides to an unauthenticated context. + */ + +const AUTHENTICATED_PAGES = [ + { name: 'home', path: '/' }, + { name: 'persons', path: '/persons' }, + { name: 'admin', path: '/admin' } +]; + +function buildAxe(page: Parameters[0]['page']) { + return new AxeBuilder({ page }).withTags(['wcag2a', 'wcag2aa']); +} + +test.describe('Accessibility — authenticated pages', () => { + for (const { name, path } of AUTHENTICATED_PAGES) { + test(`${name} page has no critical wcag2a/wcag2aa violations`, async ({ page }) => { + await page.goto(path); + await page.waitForSelector('[data-hydrated]'); + + const results = await buildAxe(page).analyze(); + + if (results.violations.length > 0) { + const summary = results.violations + .map((v) => `[${v.impact}] ${v.id}: ${v.description} (${v.nodes.length} node(s))`) + .join('\n'); + console.log(`\nAccessibility violations on ${name}:\n${summary}`); + } + + expect(results.violations).toEqual([]); + }); + } +}); + +test.describe('Accessibility — login page', () => { + test.use({ storageState: { cookies: [], origins: [] } }); + + test('login page has no critical wcag2a/wcag2aa violations', async ({ page }) => { + await page.goto('/login'); + await expect(page.getByLabel('Benutzername')).toBeVisible(); + + const results = await buildAxe(page).analyze(); + + if (results.violations.length > 0) { + const summary = results.violations + .map((v) => `[${v.impact}] ${v.id}: ${v.description} (${v.nodes.length} node(s))`) + .join('\n'); + console.log(`\nAccessibility violations on login:\n${summary}`); + } + + expect(results.violations).toEqual([]); + }); +}); -- 2.49.1 From b5580b0b24d47a777cc05269f4adb3369be61f29 Mon Sep 17 00:00:00 2001 From: Marcel Date: Sat, 28 Mar 2026 18:23:37 +0100 Subject: [PATCH 2/3] fix(#147): replace text-accent with text-primary on all text elements MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --c-accent (#a1dcd8 light / #00c7b1 dark) is a decorative mint token — 1.52:1 on white, nowhere near WCAG AA. Every place it appeared as the colour of a text label or interactive button is switched to text-primary (#012851, 16.8:1 on white) with hover:text-ink-2 for consistency. Affected: UsersTab, GroupsTab, CommentThread (Reply), DocumentList (Clear search), PdfViewer (Direkt öffnen link). Co-Authored-By: Claude Sonnet 4.6 --- frontend/src/lib/components/CommentThread.svelte | 2 +- frontend/src/lib/components/PdfViewer.svelte | 2 +- frontend/src/routes/DocumentList.svelte | 2 +- frontend/src/routes/admin/GroupsTab.svelte | 2 +- frontend/src/routes/admin/UsersTab.svelte | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/frontend/src/lib/components/CommentThread.svelte b/frontend/src/lib/components/CommentThread.svelte index de350d72..2bcf4418 100644 --- a/frontend/src/lib/components/CommentThread.svelte +++ b/frontend/src/lib/components/CommentThread.svelte @@ -277,7 +277,7 @@ onMount(async () => { {#if showReplyButton && canComment}
diff --git a/frontend/src/routes/admin/GroupsTab.svelte b/frontend/src/routes/admin/GroupsTab.svelte index d171ad0d..6eb22e53 100644 --- a/frontend/src/routes/admin/GroupsTab.svelte +++ b/frontend/src/routes/admin/GroupsTab.svelte @@ -135,7 +135,7 @@ function cancelEditGroup() {
diff --git a/frontend/src/routes/admin/UsersTab.svelte b/frontend/src/routes/admin/UsersTab.svelte index 52d40709..f6a72c32 100644 --- a/frontend/src/routes/admin/UsersTab.svelte +++ b/frontend/src/routes/admin/UsersTab.svelte @@ -78,7 +78,7 @@ let {
{m.btn_edit()} -- 2.49.1 From e28cd039534f07865f1bfa9953473edc68a25041 Mon Sep 17 00:00:00 2001 From: Marcel Date: Sat, 28 Mar 2026 18:24:45 +0100 Subject: [PATCH 3/3] fix(#147): replace text-ink/60 with text-ink-2 and add accent token guard MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit text-ink/60 produces an opacity-blended colour whose contrast is background-dependent: it passes on white (4.8:1) but fails on the sandy canvas #f0efe9 (3.97:1, below WCAG AA 4.5:1). Replace every occurrence with text-ink-2 (#4b5563, 6.6:1 on canvas — WCAG AA ✓). Also adds a warning comment above --c-accent in layout.css to prevent the text-accent misuse from recurring. Co-Authored-By: Claude Sonnet 4.6 --- frontend/src/routes/DocumentList.svelte | 2 +- frontend/src/routes/conversations/+page.svelte | 2 +- .../src/routes/conversations/ConversationTimeline.svelte | 2 +- frontend/src/routes/layout.css | 5 ++++- frontend/src/routes/persons/+page.svelte | 4 ++-- 5 files changed, 9 insertions(+), 6 deletions(-) diff --git a/frontend/src/routes/DocumentList.svelte b/frontend/src/routes/DocumentList.svelte index 71994e15..38ce333e 100644 --- a/frontend/src/routes/DocumentList.svelte +++ b/frontend/src/routes/DocumentList.svelte @@ -28,7 +28,7 @@ let { {#if canWrite}

{m.conv_heading()}

-

+

{m.conv_subtitle()}

diff --git a/frontend/src/routes/conversations/ConversationTimeline.svelte b/frontend/src/routes/conversations/ConversationTimeline.svelte index c1cac55f..6fc8bc56 100644 --- a/frontend/src/routes/conversations/ConversationTimeline.svelte +++ b/frontend/src/routes/conversations/ConversationTimeline.svelte @@ -57,7 +57,7 @@ const enrichedDocuments = $derived(