Files
tessera-ctl/.planning/quick/260925-bow-was-ist-neu-fenster-beim-ersten-anmelden/260925-bow-SUMMARY.md
T
schalli acfffa3097
Tessera CI/CD / Lint & Type Check (push) Successful in 51s
Tessera CI/CD / Tests (push) Successful in 1m20s
Tessera CI/CD / Desktop-Pakete bauen (push) Successful in 18s
Tessera CI/CD / Build & Publish Images (push) Successful in 3m13s
docs(quick-260925-bow): Was-ist-neu-Fenster
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-25 09:01:48 +02:00

168 lines
12 KiB
Markdown

---
phase: quick-260925-bow
plan: 01
quick_id: 260925-bow
status: complete
subsystem: web + api (Benutzer, Änderungsliste)
tags: [release-notice, changelog, user, prisma, a11y, rls]
requires:
- CHANGELOG.md als Bauzeit-Text (quick-260916-dcz)
- APP_VERSION als Laufzeit-ENV der API (quick-260914-ku1)
provides:
- "Spalte User.lastSeenReleaseVersion (Migration 20260925120000_user_last_seen_release)"
- "parseReleaseVersion / compareReleaseVersions / ReleaseNoticeResponse in @tessera/shared"
- "getRunningRelease() als einzige Quelle der laufenden Version"
- "GET /users/me/release-notice, POST /users/me/release-seen"
- "selectReleaseNotice (Web), Server-Aktionen, ReleaseNoticeDialog, ReleaseNoticeHost in AppShell"
affects:
- apps/web/src/components/layout/app-shell.tsx
- apps/api/src/user/user.service.ts (create)
- apps/api/src/user/admin-seed.service.ts
tech-stack:
added: []
patterns:
- "Server-Aktion in eigener 'use server'-Datei als einziger Web-Importeur der Änderungsliste neben page.tsx"
- "Fenster per React.lazy nur bei vorhandener Nachricht geladen"
key-files:
created:
- apps/api/prisma/migrations/20260925120000_user_last_seen_release/migration.sql
- apps/api/src/health/release-version.spec.ts
- apps/api/src/user/dto/release-seen.dto.ts
- apps/web/src/lib/release-notes.ts
- apps/web/src/lib/release-notes.test.ts
- apps/web/src/lib/release-notice-actions.ts
- apps/web/src/lib/release-notice-actions.test.ts
- apps/web/src/components/release-notice/release-notice-dialog.tsx
- apps/web/src/components/release-notice/release-notice-dialog.test.tsx
- apps/web/src/components/release-notice/release-notice-host.tsx
- apps/web/src/components/release-notice/release-notice-host.test.tsx
modified:
- packages/shared/src/index.ts
- apps/api/prisma/schema.prisma
- apps/api/src/health/app-version.ts
- apps/api/src/user/user.controller.ts
- apps/api/src/user/user.controller.spec.ts
- apps/api/src/user/user.service.ts
- apps/api/src/user/user.service.spec.ts
- apps/api/src/user/admin-seed.service.ts
- apps/api/src/user/admin-seed.service.spec.ts
- apps/web/src/components/changelog/changelog-view.tsx
- apps/web/src/components/layout/app-shell.tsx
- apps/web/src/messages/de.json
- apps/web/src/messages/en.json
- apps/web/src/messages/umlaut-dictionary.ts
- apps/web/src/lib/changelog.ts
- apps/web/next.config.ts
- docs/mandantentrennung-zugriffsklassifikation.md
- docs/anleitung-anwender.md
- docs/anleitung-entwicklung.md
- CHANGELOG.md
decisions:
- "Einzige Quelle der laufenden Version ist APP_VERSION der API (getRunningRelease()); das Web nimmt currentRelease aus GET /users/me/release-notice"
- "Fehlt der Abschnitt der laufenden Version in der Änderungsliste des Web-Abbilds ganz, gibt es kein Fenster; eine laufende Version nur mit „Entfernt“ zählt ebenso als ohne Abschnitt"
- "Scrollbereich des Fensters ist ein benannter section ohne tabIndex (keine neue Biome-Warnung, keine Biome-Ausnahme)"
metrics:
duration: 16min
completed: 2026-09-25
actuals:
tokens: 30400
tasks: 3
commits: 9
plan_head_before: 9225ed1bf980aa688e50b55f23162927bf95b40c
---
# Quick 260925-bow: „Was ist neu“-Fenster beim ersten Anmelden nach einem Versionswechsel Summary
Nach einem Versionswechsel zeigt Tessera jedem Benutzer beim ersten Laden des Portals einmal ein Fenster „Neu in Version X.Y.Z“ mit Neu / Verbessert / Behoben aus CHANGELOG.md (höchstens drei Versionen, neueste zuerst). Der gesehene Stand liegt pro Benutzer in der neuen Spalte `User.lastSeenReleaseVersion` und wird erst beim Schließen über `POST /users/me/release-seen` gemerkt, gebunden an Benutzer und Mandanten.
## Was gebaut wurde
**Aufgabe 1 (Tracer, DB → API → Server-Aktion → Fenster)**
- `packages/shared`: `parseReleaseVersion` (optionales `v`, drei Zifferngruppen zu je 1 bis 6 Ziffern, optional Describe-Anhang, verankert, ≤ 64 Zeichen, kanonisch ohne führende Nullen), `compareReleaseVersions` (numerisch, wirft bei Unparsebarem), `ReleaseNoticeResponse`. Warnkommentar über `WIDGET_TYPES` ergänzt.
- `getRunningRelease()` in `app-version.ts` mit Begründung, warum die API die Quelle ist.
- Spalte + Migration (`ALTER TABLE "User" ADD COLUMN "lastSeenReleaseVersion" TEXT;`), kein Standardwert, kein Backfill. **Lokal auf die DB angewendet** (`prisma migrate deploy` über 172.19.0.2); `tessera_app` hat Rechte auf Tabellenebene und damit auch auf die neue Spalte.
- `GET me/release-notice` und `POST me/release-seen` in `user.controller.ts` vor der Kennungs-Route, ohne `@Roles`, beide über `forTenant()` + `where: { id: currentUser.id }`. `ReleaseSeenDto` mit `@IsString`, `@MaxLength(32)`, `@Matches` auf kanonisches X.Y.Z; im Rumpf zusätzlich Format-, dev- und „nicht über laufend“-Prüfung; nie absenken, unparsebaren Altwert überschreiben.
- Web: `release-notes.ts` (reine Auswahl, kein Import der Änderungsliste), `release-notice-actions.ts` (`'use server'`), `ReleaseNoticeDialog` (role=dialog, aria-modal, aria-labelledby, Anfangsfokus Überschrift, Escape, Fokusfalle mit dynamischer Elementliste, Fokus-Rückgabe, keine Animation, nur Tailwind-Tokens), `ReleaseNoticeHost` (Ref-Sperre, nicht auf `/change-password`, `React.lazy`, merkt erst beim Schließen), eingebunden nur in `AppShell`. `ChangelogView` mit `variant="plain"`. Texte `releaseNotice` in de.json/en.json.
**Aufgabe 2 (Anlagewege)**
- `UserService.create()` (Admin-Anlage und beide LDAP-Wege) und `AdminSeedService` setzen `lastSeenReleaseVersion: getRunningRelease()`; kein neuer Parameter. grep-Nachweis: kein eigener `user.create` in `apps/api/src/ldap`.
- RLS-Buchführung nachgemessen mit der Gate-Schleife: **user 8/17/0** (vorher 8/14/0, +3 gebunden in `user.controller.ts`), **Summe 61/216/6** (vorher 61/213/6). Übersichts-, Summen- und Fundstellenzeile mit Vermerk **quick-260925-bow** fortgeschrieben. `rls-access-inventory.spec.ts` grün (Paar `user.controller.ts | user | gebunden` unverändert).
**Aufgabe 3 (Doku und volle Prüfungen)**
- CHANGELOG „Unveröffentlicht → Neu“, Abschnitt „Was ist neu“ im Anwenderhandbuch, Entwicklerdoku (Importregel erweitert, Versionsquelle, Endpunkte, Spalte, Folge für die Freigabe, Ausprobieren mit `APP_VERSION`), Kopfkommentare `changelog.ts` und `next.config.ts`.
## Gemessene Ergebnisse
| Prüfung | Ergebnis |
|---|---|
| API-Suite (voll) | 85 Dateien, **1435 Tests grün** |
| Web-Suite (voll) | 95 Dateien, **924 Tests grün** |
| `pnpm turbo run type-check lint` | 9/9 Tasks erfolgreich |
| Biome-Warnungen | **Web 53** (Grenze 53), **API 82** (Grenze 82) |
| Stil-Gate (Versal/Sperrschrift, `·`, `→`, rohes HTML) | leer |
| i18n-Gate `releaseNotice` de/en | vollständig |
| RLS user / Summe | 8/17/0 / 61/216/6 |
| `next build` | erfolgreich |
| Chunk-Nachweis | Satz „hochgeladene Bilder liegen jetzt im Dateibereich des Servers“: 2 Dateien unter `.next/server` (`changelog/page.js` und der Server-Chunk der Aktion), **0 unter `.next/static`**; ebenso der neue CHANGELOG-Eintrag (2 / 0). `next-env.d.ts` unverändert. |
Abgleich mit der echten CHANGELOG.md: gemerkt `1.0.0`, laufend `1.4.0` ergibt 1.4.0 (new/changed/fixed), 1.3.1 (changed/fixed), 1.3.0 (new/changed/fixed) und `omittedCount` 2, also genau das, was die Browserprüfung des Orchestrators erwartet; gemerkt `null` ergibt nur 1.4.0; gemerkt `1.4.0` ergibt kein Fenster.
## Deviations from Plan
### Auto-fixed Issues
**1. [Rule 1 - Bug] StrictMode-Doppeleffekt verwarf das Abrufergebnis im Host**
- **Found during:** Aufgabe 1 (Host-Test „StrictMode-Doppeleffekt führt nicht zu zwei Abrufen“)
- **Issue:** Ein Abbruch-Flag im Aufräumen des Effekts wurde beim simulierten Unmount gesetzt, der zweite Effektlauf fragte wegen der Ref-Sperre nicht erneut, das einzige Ergebnis ging verloren, kein Fenster.
- **Fix:** Abbruch-Flag entfernt (setState nach Unmount ist in React 19 folgenlos), Kommentar dazu.
- **Commit:** 187fb76
**2. [Rule 3 - Blocking] Umlaut-Wächter kannte „Verbessert“ nicht**
- **Found during:** Aufgabe 3 (volle Web-Suite)
- **Issue:** `umlaut-guard.spec.ts` meldete das korrekt geschriebene Wort „Verbessert“ (Gruppe `releaseNotice.section.changed`) als unbekanntes „ss“-Wort.
- **Fix:** „Verbessert“ in `UMLAUT_ALLOWLIST` (`umlaut-dictionary.ts`) mit Vermerk aufgenommen.
- **Commit:** 2aeb3e8
**3. [Rule 3 - Blocking] Scrollbereich ohne `tabIndex={0}`**
- **Issue:** Der geplante `div tabIndex={0} aria-label` erzeugte zwei neue Biome-Warnungen (`noNoninteractiveTabindex`, `useAriaPropsSupportedByRole`) und hätte die Grenze Web ≤ 53 gerissen; Biome-Ausnahmen in neuen Dateien sind laut Plan verboten.
- **Fix:** Benannter `<section aria-label={contentLabel}>` ohne tabIndex. Heutige Chromium- und WebKit-Versionen (Browser und Desktop-App) machen einen Scroll-Container ohne fokussierbaren Inhalt selbst per Tastatur erreichbar; enthält er Links, scrollt der Tab-Fokus mit. Innere Versionsblöcke sind `div` statt `section`, damit keine verschachtelten Landmarken entstehen.
- **Commit:** 187fb76
**4. [Kleinigkeit] Überschriften-Hierarchie**
- Bei einer Version sind die Gruppen `h3` (wie geplant); bei mehreren Versionen trägt die Version `h3` und die Gruppen `h4` (statt einer Versionszeile ohne Überschriftenrolle). Test entsprechend.
**5. [Hinweis] `prisma validate` braucht DATABASE_URL**
- Das Aufgabe-1-Gate ruft `prisma validate` ohne Umgebung auf; hier scheitert das nur an der fehlenden `DATABASE_URL` (P1012). Mit gesetzter URL (echte Container-IP bzw. Platzhalter) ist das Schema gültig. `prisma format` wurde bewusst NICHT verwendet, weil es die ganze Schemadatei umformatiert hätte; die neue Zeile ist von Hand eingetragen.
**6. [Hinweis] Bestehende Format-Befunde nicht angefasst**
- `biome check` meldet in `app-shell.tsx` und `changelog-view.tsx` Format-/Importreihenfolge-Befunde, die schon vorher bestanden; das Lint-Skript ist `biome lint`, die Dateien wurden deshalb nicht umformatiert. Neue Dateien sind mit `biome check --write` formatiert, ohne Ausnahmen, `any` nur in Test-Attrappen wie im Bestand.
## Hinweise für den Orchestrator (Browserprüfung, D-11)
- Die Migration ist auf der lokalen DB bereits angewendet; der laufende API-Container (altes Abbild) stört sich an der zusätzlichen nullbaren Spalte nicht. Für die Prüfung muss wie im Plan beschrieben mit `--build-arg APP_VERSION=1.4.0-1-g0000000` neu gebaut werden (der Describe-Anhang braucht mindestens 4 Hex-Zeichen nach `g`, `g0000000` passt).
- Auf `/change-password` wird nicht abgefragt; die nächste Seite danach fragt einmal.
## Threat Flags
Keine neuen Angriffsflächen außerhalb des Bedrohungsmodells: die zwei Endpunkte (T-BOW-01/02/05/06), die Migration (T-BOW-07), die Server-Aktion/Chunks (T-BOW-03) und das Markdown-Rendering über `ChangelogView` mit `rehype-sanitize` (T-BOW-04) sind dort erfasst und umgesetzt.
## Commits
| Hash | Nachricht |
|---|---|
| b35edd5 | test(260925-bow): Versionsvergleich und Was-ist-neu-Endpunkte (rot) |
| 59db32a | feat(260925-bow): gesehene Version pro Benutzer merken - Spalte, Versionsfunktionen, API |
| cf7784e | test(260925-bow): Auswahl, Server-Aktionen, Fenster und Host des Was-ist-neu-Fensters |
| 187fb76 | feat(260925-bow): Was-ist-neu-Fenster im Portal-Rahmen |
| 25c8db7 | test(260925-bow): Anlagewege tragen die laufende Version ein (rot) |
| 5ae9aaa | feat(260925-bow): neue Benutzer bekommen die laufende Version eingetragen |
| 4fa5aaf | docs(260925-bow): RLS-Buchfuehrung nachgemessen (user 8/17/0, Summe 61/216/6) |
| 2aeb3e8 | fix(260925-bow): Umlaut-Waechter kennt das korrekte Wort Verbessert |
| b3b7b5d | docs(260925-bow): Was-ist-neu-Fenster in CHANGELOG, Anwender- und Entwicklerdoku |
Nicht gepusht, kein Tag, keine Freigabe. `.planning/**` nicht committet.
## Self-Check: PASSED
Alle 11 neuen Dateien vorhanden, alle 9 Commits im Verlauf (`git rev-list --count 9225ed1..HEAD` = 9).