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 --------------------------------- */} -