From d4d4282c556425a2f465c222f6776b4be09ae53d Mon Sep 17 00:00:00 2001 From: Claude Fable 5 Date: Tue, 21 Jul 2026 14:16:20 +0200 Subject: [PATCH] #165: Reflow bei 320 px und Tastatur-Scrollbarkeit MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Vier Ursachen des seitenweiten Horizontal-Scrollens behoben: die Topbar saß mit min-content-Breite in der Grid-Spalte (min-width: 0 nach dem #100-Muster) und wickelt auf schmalen Viewports auf eine zweite Zeile (Grid-Zeile minmax, Suchtext wird zum Icon); die aufgeklappte Sidebar liegt unter 40rem als Overlay über dem Inhalt statt ihn auf einen Streifen zu quetschen; Footer wickelt; Titel-Input und Settings-Spalte schrumpfen (min-width bzw. align-items: stretch im Schmal-Layout). Der Haupt-Scrollbereich ist per tabindex=0 tastatur-scrollbar — auf den Rechtstext-Seiten gab es sonst keinen Weg, den Inhalt zu scrollen. Gemessen: 10 Ansichten bei 320 px ohne Dokument-Überlauf (vorher 892 px Inhaltsbreite); e2e content/settings-nav/sidebar/legal/page-tree/search grün. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01AGM8jo3hwoV9wsCVGfy8iq --- apps/web/src/layout/AppLayout.tsx | 5 +++- apps/web/src/layout/TopBar.tsx | 3 +- apps/web/src/styles/base.css | 50 ++++++++++++++++++++++++++++++- 3 files changed, 55 insertions(+), 3 deletions(-) diff --git a/apps/web/src/layout/AppLayout.tsx b/apps/web/src/layout/AppLayout.tsx index cd0ab18..afae63a 100644 --- a/apps/web/src/layout/AppLayout.tsx +++ b/apps/web/src/layout/AppLayout.tsx @@ -64,7 +64,10 @@ export function AppLayout(): React.JSX.Element { {!collapsed && }
-
+ {/* tabIndex: the main area is the app's scroll container; on + pages without focusable content (e.g. legal texts) keyboard + users could otherwise not scroll it (#165, WCAG 2.1.1). */} +
{/* Outside the scroll container: always visible at the bottom diff --git a/apps/web/src/layout/TopBar.tsx b/apps/web/src/layout/TopBar.tsx index ee9fff5..577d42f 100644 --- a/apps/web/src/layout/TopBar.tsx +++ b/apps/web/src/layout/TopBar.tsx @@ -96,7 +96,8 @@ export function TopBar({ sidebarCollapsed, onToggleSidebar }: TopBarProps): Reac onClick={() => setSearchOpen(true)} aria-label={t('search:open')} > - {t('search:open')} + + {t('search:open')} )} {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 {