From 0128bf71e7c0a1d4609b627bc79e929d0459bdf5 Mon Sep 17 00:00:00 2001 From: Florian Wilde Date: Sun, 23 Aug 2026 01:00:22 +0200 Subject: [PATCH] Seitenleiste bleibt stehen, Benutzermenue wieder erreichbar Am Schreibtisch war "Mein Konto" praktisch nicht zu finden - und damit auch nicht Passwortwechsel, zweiter Faktor und Passkey-Verwaltung. Die Seitenleiste hatte keine feste Hoehe und wuchs mit der Seite mit. Ihr Fuss mit dem Benutzermenue landete deshalb am Ende der SEITE statt am unteren Bildschirmrand: auf /heute bei 1632 Pixel in einem 900 Pixel hohen Fenster. Nachgemessen, nicht geraten - im Browser auf fuenf Seiten. Der Fehler sah nach nichts aus. Kein Element fehlte, nichts war kaputt, die Seite sah richtig aus. Man musste nur ganz nach unten scrollen. Behoben mit lg:sticky, lg:top-0, lg:h-dvh und lg:self-start. Das self-start gehoert dazu, weil ein Flex-Kind sonst auf die volle Zeilenhoehe gestreckt wird und die feste Hoehe wieder verliert. Die Navigation dazwischen hat bereits overflow-y-auto und scrollt bei Bedarf in sich selbst. Am Handy war es nie ein Problem - dort sitzt das Menue in der ohnehin festen Kopfzeile. Neu: e2e/benutzermenue.spec.ts fragt nicht, ob das Menue im DOM ist, sondern ob es im sichtbaren Bereich steht - auf fuenf Seiten, an drei Scrollpositionen, auf einem flachen Laptopfenster und am Handy. Die Position in der Mitte ist Absicht: Am Seitenende wird eine mitwachsende Leiste zufaellig doch sichtbar, der Test waere gruen und der Fehler trotzdem da. Beide Sichtbarkeitstests sind gegen den kaputten Zustand gegengeprueft und schlagen dort an. Refs #1 --- CHANGELOG.md | 40 +++++++++++- README.md | 4 +- docs/INSTALLATION.md | 12 ++-- docs/ROADMAP.md | 2 +- e2e/benutzermenue.spec.ts | 117 ++++++++++++++++++++++++++++++++++ package.json | 2 +- src/components/app-huelle.tsx | 17 ++++- src/lib/version.ts | 2 +- 8 files changed, 182 insertions(+), 14 deletions(-) create mode 100644 e2e/benutzermenue.spec.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index c9b2673..c982cbb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -26,6 +26,43 @@ Wiederherstellung*. _Noch nichts._ +## [1.1.2] – 2026-08-23 + +### Behoben + +- **„Mein Konto" war am Schreibtisch praktisch nicht zu finden** + ([#1](https://git.wilde-server.de/florian.wilde/betreuungs-cockpit/issues/1)). + Damit kam man dort auch nicht an den Passwortwechsel, den zweiten Faktor + und die frisch eingebaute Passkey-Verwaltung. + + Ursache: Die Seitenleiste hatte keine feste Höhe und wuchs mit der Seite + mit. Ihr Fuß — und damit das Benutzermenü — landete deshalb am **Ende der + Seite** statt am unteren Bildschirmrand. Auf „Heute" waren das 1632 Pixel + bei 900 Pixel Fensterhöhe: Man musste bis ganz nach unten scrollen, um + an sein eigenes Konto zu kommen. + + Der Fehler sah nach nichts aus. Kein Element fehlte, nichts war kaputt, + die Seite sah richtig aus — und genau deshalb sucht man an der falschen + Stelle. + + Die Leiste bleibt jetzt stehen (`sticky`, Fensterhöhe). Kopf, + Betreutenauswahl und Fuß sind immer sichtbar; die Navigation dazwischen + scrollt bei Bedarf in sich selbst. Am Handy war es nie ein Problem — + dort sitzt das Menü in der ohnehin festen Kopfzeile. + +### Geprüft + +- Fünf End-to-End-Tests (`e2e/benutzermenue.spec.ts`) fragen nicht, ob das + Menü **im DOM** ist, sondern ob es **im sichtbaren Bereich** steht: auf + fünf verschiedenen Seiten, an drei Scrollpositionen, auf einem flachen + Laptopfenster (1280 × 620) und am Handy. + + Die Scrollposition in der Mitte ist Absicht. Nur oben und unten zu messen + hätte nicht gereicht: Am Seitenende wird eine mitwachsende Leiste + zufällig doch sichtbar — der Test wäre grün gewesen und der Fehler + trotzdem da. Beide Sichtbarkeitstests sind gegen den kaputten Zustand + gegengeprüft und schlagen dort an. + ## [1.1.1] – 2026-08-23 ### Behoben @@ -511,7 +548,8 @@ Was davon wann angegangen wird, steht in [`docs/ROADMAP.md`](docs/ROADMAP.md). --- -[Unveröffentlicht]: https://git.wilde-server.de/florian.wilde/betreuungs-cockpit/compare/v1.1.1...HEAD +[Unveröffentlicht]: https://git.wilde-server.de/florian.wilde/betreuungs-cockpit/compare/v1.1.2...HEAD +[1.1.2]: https://git.wilde-server.de/florian.wilde/betreuungs-cockpit/compare/v1.1.1...v1.1.2 [1.1.1]: https://git.wilde-server.de/florian.wilde/betreuungs-cockpit/compare/v1.1.0...v1.1.1 [1.1.0]: https://git.wilde-server.de/florian.wilde/betreuungs-cockpit/compare/v1.0.5...v1.1.0 [1.0.5]: https://git.wilde-server.de/florian.wilde/betreuungs-cockpit/compare/v1.0.4...v1.0.5 diff --git a/README.md b/README.md index e9032f2..4f3f0ec 100644 --- a/README.md +++ b/README.md @@ -580,11 +580,11 @@ Fehler, die während des Baus aufgefallen sind. | | | nachzuzählen mit | |---|---:|---| -| Zeilen TypeScript/TSX | 72.080 | `git ls-files "*.ts" "*.tsx" \| xargs wc -l` | +| Zeilen TypeScript/TSX | 73.774 | `git ls-files "*.ts" "*.tsx" \| xargs wc -l` | | Prisma-Modelle | 33 | `grep -c '^model ' prisma/schema.prisma` | | Seiten | 79 | `find src/app -name page.tsx \| wc -l` | | Unit-Tests (Vitest) | 500 | `npm test` | -| End-to-End-Tests (Playwright) | 37 | `npm run test:e2e` | +| End-to-End-Tests (Playwright) | 45 | `npm run test:e2e` — 26 Tests über zwei Geräteprofile, geräteabhängige übersprungen | | TypeScript-Fehler | 0 | `npm run typecheck` | Die Befehle stehen dabei, weil die Zahlen sonst altern, ohne dass es diff --git a/docs/INSTALLATION.md b/docs/INSTALLATION.md index 68ab85d..5f1b0f8 100644 --- a/docs/INSTALLATION.md +++ b/docs/INSTALLATION.md @@ -131,14 +131,14 @@ cd /opt/betreuungs-cockpit # Auf die letzte veröffentlichte Fassung wechseln, # nicht auf den laufenden Entwicklungsstand -git checkout v1.1.1 +git checkout v1.1.2 ``` ### Kontrolle ```bash ls docker-compose.yml Dockerfile .env.example # alle drei da -git describe --tags # v1.1.1 +git describe --tags # v1.1.2 ``` --- @@ -299,7 +299,7 @@ betreuungs-cockpit-sicherung-1 Up ```bash # Antwortet die Anwendung? (Port anpassen, falls du APP_PORT gesetzt hast) curl -s http://127.0.0.1:3000/api/gesundheit -# {"status":"bereit","version":"1.1.1","commit":"..."} +# {"status":"bereit","version":"1.1.2","commit":"..."} ``` Kommt hier nichts, sieh in die Logs: @@ -649,7 +649,7 @@ Erst **vom Proxy-Server aus** — kommt er überhaupt an? ```bash curl -s http://10.0.0.5:3000/api/gesundheit -# {"status":"bereit","version":"1.1.1",...} +# {"status":"bereit","version":"1.1.2",...} ``` Kommt hier nichts, liegt es an 7a: falsche `APP_BIND`, Firewall, oder der @@ -700,7 +700,7 @@ Zum Schluss: **Fristen → Pflichtfristen ableiten**. - Du kommst mit E-Mail, Passwort und dem Code aus der App herein. - Unter **Fristen** stehen Einträge mit Paragraphenbezug. - Unter **Protokoll** siehst du deine eigene Anmeldung. -- Unten links in der Seitenleiste steht `Version 1.1.1 · `. +- Unten links in der Seitenleiste steht `Version 1.1.2 · `. --- @@ -913,7 +913,7 @@ git fetch --tags git log --oneline HEAD..origin/main # 3. Umschalten -git checkout v1.1.1 +git checkout v1.1.2 # 4. Neu bauen und starten GIT_COMMIT=$(git rev-parse --short HEAD) BUILD_ZEIT=$(date -Iseconds) docker compose up -d --build diff --git a/docs/ROADMAP.md b/docs/ROADMAP.md index 4ac534b..f67b708 100644 --- a/docs/ROADMAP.md +++ b/docs/ROADMAP.md @@ -9,7 +9,7 @@ Zeit spart. Er steht weit unten, wenn er nur schön wäre. Diese Datei ist eine Absichtserklärung, kein Vertrag. Was sich im Betrieb als wichtiger herausstellt, rutscht nach oben. -Stand: 23.08.2026 · Aktuelle Fassung: **1.1.1** ([CHANGELOG](../CHANGELOG.md)) +Stand: 23.08.2026 · Aktuelle Fassung: **1.1.2** ([CHANGELOG](../CHANGELOG.md)) --- diff --git a/e2e/benutzermenue.spec.ts b/e2e/benutzermenue.spec.ts new file mode 100644 index 0000000..2ad132a --- /dev/null +++ b/e2e/benutzermenue.spec.ts @@ -0,0 +1,117 @@ +import { expect, test } from '@playwright/test' +import type { Page } from '@playwright/test' +import { anmelden } from './hilfen' + +/** + * Das Benutzermenü muss erreichbar sein, ohne danach zu suchen. + * + * Anlass ist Issue #1: Am Schreibtisch war „Mein Konto" praktisch nicht + * zu finden — und damit auch nicht der Passwortwechsel, der zweite Faktor + * und die Passkey-Verwaltung. Die Seitenleiste hatte keine feste Höhe und + * wuchs mit der Seite mit; ihr Fuß landete deshalb am Ende der Seite statt + * am unteren Bildschirmrand. Auf `/heute` waren das 1632 Pixel bei 900 + * Pixel Fensterhöhe. + * + * Der Fehler sah nach nichts aus: Kein Element fehlte, nichts war kaputt, + * die Seite sah richtig aus. Man musste nur ans Seitenende scrollen — und + * darauf kommt niemand, der eine Seitenleiste vor sich hat. + * + * Geprüft wird deshalb nicht „ist das Element im DOM", sondern „steht es + * im sichtbaren Bereich". + */ + +/** Der Auslöser des Benutzermenüs in der Seitenleiste am Schreibtisch. */ +function ausloeserInSeitenleiste(seite: Page) { + return seite.locator('aside button[aria-haspopup="menu"]') +} + +/** Steht das Element vollständig im Fenster, ohne zu scrollen? */ +async function imSichtbarenBereich(seite: Page, auswahl: string): Promise { + return seite.evaluate((sel) => { + const el = document.querySelector(sel) + if (!el) return false + const k = el.getBoundingClientRect() + return k.top >= 0 && k.bottom <= window.innerHeight && k.height > 0 + }, auswahl) +} + +test.describe('Benutzermenü am Schreibtisch', () => { + test.skip(({ isMobile }) => Boolean(isMobile), 'Am Handy sitzt es in der Kopfzeile.') + + test('steht auch auf langen Seiten im Bild, ohne zu scrollen', async ({ page }) => { + await anmelden(page) + + // Seiten, die laenger sind als das Fenster - genau dort trat der + // Fehler auf. /fristen ist als Gegenprobe kurz. + for (const ziel of ['/heute', '/vorgaenge', '/dokumente', '/finanzen', '/fristen']) { + await page.goto(ziel) + await page.waitForLoadState('networkidle') + + const sichtbar = await imSichtbarenBereich(page, 'aside button[aria-haspopup="menu"]') + expect(sichtbar, `Benutzermenü nicht im Bild auf ${ziel}`).toBe(true) + } + }) + + test('bleibt an jeder Scrollposition stehen', async ({ page }) => { + await anmelden(page) + await page.goto('/heute') + await page.waitForLoadState('networkidle') + + // Bewusst auch die Mitte pruefen. Nur oben und unten zu messen genuegt + // nicht: Eine mitwachsende Leiste ist am Seitenende zufaellig doch + // sichtbar - der Test waere dann gruen und der Fehler trotzdem da. + for (const anteil of [0, 0.5, 1]) { + await page.evaluate((a) => { + const hoehe = document.documentElement.scrollHeight - window.innerHeight + window.scrollTo(0, hoehe * a) + }, anteil) + await page.waitForTimeout(250) + + const sichtbar = await imSichtbarenBereich(page, 'aside button[aria-haspopup="menu"]') + expect(sichtbar, `Benutzermenü nicht im Bild bei ${anteil * 100}% Scrollposition`).toBe(true) + } + }) + + test('führt in zwei Klicks nach „Mein Konto"', async ({ page }) => { + await anmelden(page) + await page.goto('/heute') + + await ausloeserInSeitenleiste(page).click() + await page.getByRole('menuitem', { name: 'Mein Konto' }).click() + + await page.waitForURL('**/konto') + // Und dort steht auch, worum es im Issue ging. + await expect(page.getByRole('heading', { name: 'Mein Konto' })).toBeVisible() + await expect(page.getByRole('heading', { name: 'Passwort ändern' })).toBeVisible() + await expect(page.getByRole('heading', { name: 'Passkeys' })).toBeVisible() + }) + + test('bleibt auf einem flachen Fenster erreichbar', async ({ page }) => { + // Ein Laptop mit 1280x620 ist knapp: Kopf, Betreutenauswahl, + // Navigation und Fuss muessen zusammen hineinpassen. Die Navigation + // dazwischen darf scrollen, der Fuss nicht verschwinden. + await anmelden(page) + await page.setViewportSize({ width: 1280, height: 620 }) + await page.goto('/heute') + await page.waitForLoadState('networkidle') + + expect(await imSichtbarenBereich(page, 'aside button[aria-haspopup="menu"]')).toBe(true) + }) +}) + +test.describe('Benutzermenü am Handy', () => { + test.skip(({ isMobile }) => !isMobile, 'Am Schreibtisch sitzt es in der Seitenleiste.') + + test('steht in der Kopfzeile und bleibt beim Scrollen stehen', async ({ page }) => { + await anmelden(page) + await page.goto('/heute') + await page.waitForLoadState('networkidle') + + expect(await imSichtbarenBereich(page, 'header button[aria-haspopup="menu"]')).toBe(true) + + await page.evaluate(() => window.scrollTo(0, document.documentElement.scrollHeight)) + await page.waitForTimeout(300) + + expect(await imSichtbarenBereich(page, 'header button[aria-haspopup="menu"]')).toBe(true) + }) +}) diff --git a/package.json b/package.json index dd60e6c..55ebf16 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "betreuungs-cockpit", - "version": "1.1.1", + "version": "1.1.2", "private": true, "description": "Arbeits- und Dokumentationswerkzeug fuer die ehrenamtliche rechtliche Betreuung", "engines": { diff --git a/src/components/app-huelle.tsx b/src/components/app-huelle.tsx index 49b8a14..d111bcf 100644 --- a/src/components/app-huelle.tsx +++ b/src/components/app-huelle.tsx @@ -56,8 +56,21 @@ export function AppHuelle({ Zum Inhalt springen - {/* --- Seitenleiste am Schreibtisch --------------------------------- */} -