From 057992faafec0049f86309abd13ed2f3b1fa4815 Mon Sep 17 00:00:00 2001 From: Claude Fable 5 Date: Tue, 21 Jul 2026 14:04:36 +0200 Subject: [PATCH 1/2] =?UTF-8?q?#164:=20ARIA-Semantik=20=E2=80=94=20Editorf?= =?UTF-8?q?l=C3=A4che,=20Autocomplete-Listboxen,=20Sidebar,=20Toolbar?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Die Editorfläche bekommt einen lokalisierten zugänglichen Namen und ist im Lesemodus role=document statt eines unbenannten Textfelds (setOptions im selben Layout-Effekt wie setEditable). Eingeklappte Sidebar zusätzlich inert (aria-hidden allein ließ fokussierbare Kinder im Tab-Weg). Die li-Zwischenknoten der Listboxen (Wikilink-/Mention-Autocomplete, Suchergebnisse) sind role=presentation, damit listbox→option wieder eine gültige Eltern-Kind-Beziehung ist. Toolbar: Pfeiltasten-Navigation über die Controls (native Selects behalten ihre Pfeiltasten) und ein sprechendes Toolbar-Label statt des Absatz-Buttons-Labels. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01AGM8jo3hwoV9wsCVGfy8iq --- apps/web/src/editor/MentionAutocomplete.tsx | 2 +- apps/web/src/editor/Toolbar.tsx | 23 +++++++++++++++++++- apps/web/src/editor/WikilinkAutocomplete.tsx | 2 +- apps/web/src/layout/Sidebar.tsx | 1 + apps/web/src/pages/PageEditorPage.tsx | 14 +++++++++++- apps/web/src/search/SearchPalette.tsx | 2 +- packages/shared/i18n/de/editor.json | 4 +++- packages/shared/i18n/en/editor.json | 4 +++- 8 files changed, 45 insertions(+), 7 deletions(-) diff --git a/apps/web/src/editor/MentionAutocomplete.tsx b/apps/web/src/editor/MentionAutocomplete.tsx index 873fcf6..17281c2 100644 --- a/apps/web/src/editor/MentionAutocomplete.tsx +++ b/apps/web/src/editor/MentionAutocomplete.tsx @@ -121,7 +121,7 @@ export function MentionAutocomplete({ editor }: { editor: Editor }): React.JSX.E style={{ position: 'fixed', left: state.coords.left, top: state.coords.bottom + 4 }} > {suggestions.map((user, index) => ( -
  • +
  • )} {searchOpen && user && setSearchOpen(false)} />} diff --git a/apps/web/src/styles/base.css b/apps/web/src/styles/base.css index 3cedb5a..4dc4299 100644 --- a/apps/web/src/styles/base.css +++ b/apps/web/src/styles/base.css @@ -53,7 +53,9 @@ button { /* Layout skeleton */ .app { display: grid; - grid-template-rows: var(--topbar-height) 1fr; + /* minmax: the topbar row may grow when its controls wrap on narrow + viewports (issue #165, WCAG 1.4.10). */ + grid-template-rows: minmax(var(--topbar-height), auto) 1fr; height: 100%; } @@ -70,6 +72,10 @@ button { align-items: center; gap: var(--space-4); padding: 0 var(--space-4); + /* Same #100 pattern as .app-body: without this the topbar's min-content + width sizes the shared grid column and forces page-level horizontal + scrolling on narrow viewports (issue #165, WCAG 1.4.10). */ + min-width: 0; border-bottom: 1px solid var(--color-border); background: var(--color-bg); /* Own stacking context above the scrolling `.main` content: the nav comes @@ -91,6 +97,39 @@ button { flex: 1; } +/* Narrow viewports (issue #165, WCAG 1.4.10): the topbar wraps onto a + second row instead of pushing the page wider than the viewport; every + control stays visible and reachable. The search button drops to its + icon (the aria-label carries the name). The expanded sidebar becomes + an overlay above the content — 256 px beside a 320 px viewport would + leave the content a sliver and force page-level horizontal scrolling. */ +@media (max-width: 40rem) { + .topbar { + flex-wrap: wrap; + row-gap: 0; + padding: var(--space-1) var(--space-2); + } + + .topbar__search-text { + display: none; + } + + .app-body { + position: relative; + } + + .sidebar:not(.sidebar--collapsed) { + position: absolute; + inset: 0 auto 0 0; + z-index: 25; + box-shadow: 2px 0 8px rgb(0 0 0 / 0.15); + } + + .sidebar-resizer { + display: none; + } +} + .sidebar { width: var(--sidebar-width); flex-shrink: 0; @@ -715,6 +754,9 @@ button { align-items: center; gap: var(--space-2); flex-shrink: 0; + /* Wrap instead of widening the page on narrow viewports (#165). */ + flex-wrap: wrap; + min-width: 0; padding: var(--space-2) var(--space-8); border-top: 1px solid var(--color-border); background: var(--color-bg); @@ -958,6 +1000,9 @@ button { .editor-page__title { flex: 1; + /* An input's intrinsic size-attribute width must not set the page's + minimum width on narrow viewports (#165). */ + min-width: 0; border: none; background: none; font-family: var(--font-heading); @@ -3784,6 +3829,9 @@ ul[data-type='task_list'] li p:last-of-type { @media (max-width: 60rem) { .settings-layout { flex-direction: column; + /* Override the row layout's flex-start: stacked, the content column + must stretch to the viewport instead of its widest control (#165). */ + align-items: stretch; } .settings-nav { -- 2.45.2