From 156efe8b312744daf5970f517bc064e223e3eb93 Mon Sep 17 00:00:00 2001 From: Marcel Date: Sat, 25 Apr 2026 16:35:40 +0200 Subject: [PATCH] =?UTF-8?q?fix(bulk-edit):=20a11y=20+=20i18n=20hardening?= =?UTF-8?q?=20(Leonie=20blockers=201=E2=80=934=20+=20quick=20concerns)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit B1 — i18n the archive-box / archive-folder labels and add helper text. Karton/Mappe were hardcoded German and broke EN/ES locales (WCAG 3.1.2). B2 — drop the hardcoded German aria-label on the onboarding callout. role="note" + the visible localised text is self-describing; the redundant label was overriding the translated content for AT users on EN/ES. B3 — Escape clears the bulk selection while the bar is visible. Adds an "Esc: Auswahl aufheben" hint visible at ≥ sm (WCAG 2.1.1). B4 — /documents and /enrich reserve pb-32 when the bulk-selection bar is visible so it doesn't occlude the last row or pagination (WCAG 1.4.10). Folded in three Leonie quick-concerns: - C5: badge text-[10px] → text-[11px], raw text-gray-600 → design-token text-ink-2 (dark-mode safe) - C7: aria-live="polite" on bulk-selection-count - C11: "Alles aufheben" → "Auswahl aufheben" (DE/EN/ES) — disambiguates from "discard the operation entirely" Refs #225, PR #331 Co-Authored-By: Claude Sonnet 4.6 --- frontend/messages/de.json | 8 ++++- frontend/messages/en.json | 8 ++++- frontend/messages/es.json | 8 ++++- .../document/BulkDocumentEditLayout.svelte | 6 ++-- .../document/BulkSelectionBar.svelte | 34 +++++++++++++++---- .../document/BulkSelectionBar.svelte.spec.ts | 23 +++++++++++++ .../document/DescriptionSection.svelte | 10 +++--- .../document/FieldLabelBadge.svelte | 2 +- .../document/FieldLabelBadge.svelte.spec.ts | 6 ++-- frontend/src/routes/documents/+page.svelte | 8 ++++- frontend/src/routes/enrich/+page.svelte | 4 ++- 11 files changed, 95 insertions(+), 22 deletions(-) diff --git a/frontend/messages/de.json b/frontend/messages/de.json index 5642db93..8088ef6b 100644 --- a/frontend/messages/de.json +++ b/frontend/messages/de.json @@ -887,5 +887,11 @@ "bulk_edit_retry": "Erneut versuchen", "bulk_edit_title": "Massenbearbeitung", "bulk_edit_save_button": "Anwenden", - "error_bulk_edit_too_many_ids": "Maximal 500 Dokumente pro Anfrage." + "error_bulk_edit_too_many_ids": "Maximal 500 Dokumente pro Anfrage.", + "form_label_archive_box": "Karton", + "form_helper_archive_box": "Welcher Karton im Archiv?", + "form_label_archive_folder": "Mappe", + "form_helper_archive_folder": "Welche Mappe innerhalb des Kartons?", + "bulk_edit_clear_selection": "Auswahl aufheben", + "bulk_edit_clear_hint_keyboard": "Esc: Auswahl aufheben" } diff --git a/frontend/messages/en.json b/frontend/messages/en.json index 50f2399f..d5796dbf 100644 --- a/frontend/messages/en.json +++ b/frontend/messages/en.json @@ -887,5 +887,11 @@ "bulk_edit_retry": "Retry", "bulk_edit_title": "Bulk edit", "bulk_edit_save_button": "Apply", - "error_bulk_edit_too_many_ids": "Maximum 500 documents per request." + "error_bulk_edit_too_many_ids": "Maximum 500 documents per request.", + "form_label_archive_box": "Box", + "form_helper_archive_box": "Which box in the archive?", + "form_label_archive_folder": "Folder", + "form_helper_archive_folder": "Which folder inside the box?", + "bulk_edit_clear_selection": "Clear selection", + "bulk_edit_clear_hint_keyboard": "Esc: clear selection" } diff --git a/frontend/messages/es.json b/frontend/messages/es.json index 0cd956ab..2678eee0 100644 --- a/frontend/messages/es.json +++ b/frontend/messages/es.json @@ -887,5 +887,11 @@ "bulk_edit_retry": "Reintentar", "bulk_edit_title": "Edición masiva", "bulk_edit_save_button": "Aplicar", - "error_bulk_edit_too_many_ids": "Máximo 500 documentos por solicitud." + "error_bulk_edit_too_many_ids": "Máximo 500 documentos por solicitud.", + "form_label_archive_box": "Caja", + "form_helper_archive_box": "¿Qué caja del archivo?", + "form_label_archive_folder": "Carpeta", + "form_helper_archive_folder": "¿Qué carpeta dentro de la caja?", + "bulk_edit_clear_selection": "Limpiar selección", + "bulk_edit_clear_hint_keyboard": "Esc: limpiar selección" } diff --git a/frontend/src/lib/components/document/BulkDocumentEditLayout.svelte b/frontend/src/lib/components/document/BulkDocumentEditLayout.svelte index d9576c2c..57d21dd9 100644 --- a/frontend/src/lib/components/document/BulkDocumentEditLayout.svelte +++ b/frontend/src/lib/components/document/BulkDocumentEditLayout.svelte @@ -379,10 +379,12 @@ async function retrySave() { > {#if mode === 'edit'} + and that tags/receivers are added rather than replaced. + No aria-label — role=note + the visible text content is + self-describing; an aria-label would override that text for + AT users on non-DE locales. -->
diff --git a/frontend/src/lib/components/document/BulkSelectionBar.svelte b/frontend/src/lib/components/document/BulkSelectionBar.svelte index 5a16897c..14e02c45 100644 --- a/frontend/src/lib/components/document/BulkSelectionBar.svelte +++ b/frontend/src/lib/components/document/BulkSelectionBar.svelte @@ -6,6 +6,7 @@ import { bulkSelectionStore } from '$lib/stores/bulkSelection.svelte'; let { canWrite }: { canWrite: boolean } = $props(); const count = $derived(bulkSelectionStore.size); +const visible = $derived(canWrite && count > 0); function openBulkEdit() { goto('/documents/bulk-edit'); @@ -14,16 +15,37 @@ function openBulkEdit() { function clearAll() { bulkSelectionStore.clear(); } + +// Escape clears the selection — keyboard escape hatch when the user has +// drilled into a 50-row selection and wants to bail without Tab-ing through +// the whole footer (WCAG 2.1.1). +function onEscape(e: KeyboardEvent) { + if (e.key === 'Escape' && visible) { + clearAll(); + } +} -{#if canWrite && count > 0} + + +{#if visible}
- - {m.bulk_edit_n_selected({ count })} - +
+ + {m.bulk_edit_n_selected({ count })} + + +
diff --git a/frontend/src/lib/components/document/FieldLabelBadge.svelte b/frontend/src/lib/components/document/FieldLabelBadge.svelte index ac59e552..6a694dca 100644 --- a/frontend/src/lib/components/document/FieldLabelBadge.svelte +++ b/frontend/src/lib/components/document/FieldLabelBadge.svelte @@ -10,7 +10,7 @@ const text = $derived( {text} diff --git a/frontend/src/lib/components/document/FieldLabelBadge.svelte.spec.ts b/frontend/src/lib/components/document/FieldLabelBadge.svelte.spec.ts index 9895e0c0..d52213ad 100644 --- a/frontend/src/lib/components/document/FieldLabelBadge.svelte.spec.ts +++ b/frontend/src/lib/components/document/FieldLabelBadge.svelte.spec.ts @@ -21,10 +21,8 @@ describe('FieldLabelBadge', () => { .toHaveTextContent('wird ersetzt'); }); - it('uses text-gray-600 for WCAG-AA contrast on muted backgrounds', async () => { + it('uses the design-system text-ink-2 token (not raw Tailwind palette)', async () => { render(FieldLabelBadge, { variant: 'replace' }); - await expect - .element(page.getByTestId('field-label-badge-replace')) - .toHaveClass(/text-gray-600/); + await expect.element(page.getByTestId('field-label-badge-replace')).toHaveClass(/text-ink-2/); }); }); diff --git a/frontend/src/routes/documents/+page.svelte b/frontend/src/routes/documents/+page.svelte index 4bc00f64..82effc6f 100644 --- a/frontend/src/routes/documents/+page.svelte +++ b/frontend/src/routes/documents/+page.svelte @@ -200,7 +200,13 @@ $effect(() => { {m.nav_documents()} – Familienarchiv -
+ +
0 && data.canWrite} +>

{m.nav_documents()}

-
+ +
0 && canWrite}>