From 2077d92c09524d42893fe78666d005ad02a3a060 Mon Sep 17 00:00:00 2001 From: Claude Fable 5 Date: Tue, 21 Jul 2026 14:26:54 +0200 Subject: [PATCH 1/6] #166: Skip-Link, verstecktes Seiten-h1, Resizer in die Nav-Landmarke MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Skip-Link als erster Tab-Stopp springt auf #main; die angemeldete Seitenansicht bekommt ein visually-hidden h1 (der sichtbare Titel ist ein Input, der jetzt auch ein aria-label trägt); der Sidebar-Resizer wandert in die nav-Landmarke (absolut an der Kante positioniert), damit kein Inhalt außerhalb von Landmarken liegt. Zwei e2e-Locator auf das Sidebar-Formular gescoped — das Editor-Titelfeld matcht seit dem neuen Label ebenfalls auf /title|titel/i. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01AGM8jo3hwoV9wsCVGfy8iq --- apps/web/e2e/content.spec.ts | 2 +- apps/web/e2e/sidebar.spec.ts | 2 +- apps/web/src/layout/AppLayout.tsx | 21 +++++++++++++++++---- apps/web/src/layout/Sidebar.tsx | 5 ++++- apps/web/src/pages/PageEditorPage.tsx | 4 ++++ apps/web/src/styles/base.css | 27 +++++++++++++++++++++++++-- packages/shared/i18n/de/common.json | 6 ++++-- packages/shared/i18n/de/editor.json | 3 ++- packages/shared/i18n/en/common.json | 6 ++++-- packages/shared/i18n/en/editor.json | 3 ++- 10 files changed, 64 insertions(+), 15 deletions(-) diff --git a/apps/web/e2e/content.spec.ts b/apps/web/e2e/content.spec.ts index 993712f..2c59712 100644 --- a/apps/web/e2e/content.spec.ts +++ b/apps/web/e2e/content.spec.ts @@ -62,7 +62,7 @@ test('page lifecycle: create via the sidebar, rename, appears in the sidebar', a const page = await context.newPage(); await page.goto(`/p/${pond.slug}`); await page.getByRole('button', { name: /new page|neue seite/i }).click(); - await page.getByLabel(/title|titel/i).fill(title); + await page.locator('.sidebar').getByLabel(/title|titel/i).fill(title); await page.getByRole('button', { name: /create|erstellen/i }).click(); await expect(page.locator('.sidebar__page--active')).toHaveText(title); diff --git a/apps/web/e2e/sidebar.spec.ts b/apps/web/e2e/sidebar.spec.ts index f101a5e..2d4a571 100644 --- a/apps/web/e2e/sidebar.spec.ts +++ b/apps/web/e2e/sidebar.spec.ts @@ -110,7 +110,7 @@ test('new-page flow: button opens a title prompt and the editor opens on create' await page.goto(`/p/${pond.slug}`); await page.getByRole('button', { name: /new page|neue seite/i }).click(); - await page.getByLabel(/title|titel/i).fill(title); + await page.locator('.sidebar').getByLabel(/title|titel/i).fill(title); await page.getByRole('button', { name: /create|erstellen/i }).click(); await expect(page).toHaveURL(new RegExp(`/p/${pond.slug}/.+`)); diff --git a/apps/web/src/layout/AppLayout.tsx b/apps/web/src/layout/AppLayout.tsx index afae63a..5d8ce8e 100644 --- a/apps/web/src/layout/AppLayout.tsx +++ b/apps/web/src/layout/AppLayout.tsx @@ -1,4 +1,5 @@ import { useEffect, useMemo, useState } from 'react'; +import { useTranslation } from 'react-i18next'; import { Outlet } from 'react-router-dom'; import { usePersistentState } from '../lib/use-persistent-state'; @@ -10,6 +11,7 @@ import { clampSidebarWidth, SIDEBAR_DEFAULT_WIDTH_REM, SidebarResizer } from './ import { TopBar } from './TopBar'; export function AppLayout(): React.JSX.Element { + const { t } = useTranslation(); const [sidebarCollapsed, setSidebarCollapsed] = usePersistentState('ui.sidebar.collapsed', false); const [sidebarWidth, setSidebarWidth] = usePersistentState( 'ui.sidebar.width', @@ -53,6 +55,10 @@ export function AppLayout(): React.JSX.Element {
+ {/* First tab stop: jump over topbar + sidebar (#166, WCAG 2.4.1). */} + + {t('layout.skipToContent')} + setSidebarCollapsed(!sidebarCollapsed)} @@ -61,13 +67,20 @@ export function AppLayout(): React.JSX.Element { className="app-body" style={{ '--sidebar-width': `${widthRem}rem` } as React.CSSProperties} > - - {!collapsed && } +