168 lines
12 KiB
Markdown
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).
|