diff --git a/.planning/STATE.md b/.planning/STATE.md index cf7e98e..49cdc2c 100644 --- a/.planning/STATE.md +++ b/.planning/STATE.md @@ -4,10 +4,10 @@ milestone: v1.2 current_phase: 18 current_phase_name: desktop-client-fertigstellen status: verified -stopped_at: "Quick 260921-9ie (WINDOWS #35) abgeschlossen und verifiziert; als naechstes WINDOWS #36 (stille 403-Antworten in der Benutzerverwaltung)" -last_updated: "2026-09-21T05:20:00.000Z" +stopped_at: "WINDOWS #35 und #36 abgeschlossen und verifiziert (7 Commits, alle Gatter gruen); nicht gepusht — Push und CI-Lauf stehen noch aus" +last_updated: "2026-09-21T05:45:00.000Z" last_activity: 2026-09-21 -last_activity_desc: Quick 260921-9ie — WINDOWS #35 geschlossen: Biome 2.5.0 lauffaehig, pnpm lint prueft echt (5/5 Workspaces, Exit 0), Gegenprobe rot; Sicherheitsregeln unangetastet, kein Quellcode umformatiert +last_activity_desc: Quick 260921-9ie und 260921-a1d — WINDOWS #35 (Biome lauffaehig, Lint-Tor scharf) und #36 (Benutzerverwaltung meldet 403 sichtbar, keine Aktionsknoepfe auf SUPER_ADMIN-Zeilen fuer ADMIN) geschlossen und verifiziert state_head: 4d485432c003a6caf68f6d85aff7de0bd27794e2 progress: total_phases: 18 @@ -31,7 +31,7 @@ See: .planning/PROJECT.md (updated 2026-07-17) Phase: 18 (desktop-client-fertigstellen) — COMPLETE (2026-09-17, Verifikation passed, Windows-Bedienprobe bestanden) Plan: 6 of 6 Status: Alle 18 Phasen abgeschlossen; Version 1.2.0 freigegeben. Kein laufender Meilenstein. Nach 1.2.0 auf main (Beta): Bildmarke in Akzentfarbe, CI-Desktop-Skip, Favoriten-Symbol/-Sortierung, Desktop-Server-Adresse, Update in der App (signiert), Versionszeile auf der Setup-Seite — alles verifiziert und auf VM/CI nachgewiesen -Last activity: 2026-09-21 - Quick 260921-9ie (WINDOWS #35): Biome 2.5.0 lauffaehig, `pnpm lint` prueft echt in allen fuenf Workspaces — gruen auf dem Bestand (Exit 0), rot bei echtem Verstoss (Exit 1); Sicherheitsregeln unangetastet, kein Quellcode umformatiert +Last activity: 2026-09-21 - Quick 260921-a1d (WINDOWS #36): Benutzerverwaltung meldet abgewiesene Server-Antworten sichtbar (drei Stellen), ADMIN bekommt in der SUPER_ADMIN-Zeile keine Aktionsknoepfe mehr; apps/api unangetastet, Web-Tests 66/459 gruen Progress: [██████████] 99% @@ -445,6 +445,7 @@ Gerettet aus `.continue-here.md`. Relevant fuer die noch offenen Live-Tests. | 260918-gza | **Fehlermeldung: Herkunft ausweisen (Browser/Desktop-App, Betriebssystem, App-Version).** Betreff traegt direkt nach `[Tessera Fehlermeldung]` ein Kuerzel `[Browser]` / `[Desktop/Windows]` / `[Desktop/Linux]` (`[Desktop]` bei altem Client ohne Details); Mailtext bekommt die Zeile `Herkunft:` — Browser: `Browser — auf ` aus dem User-Agent (reine Regex-Helfer `origin.ts`, keine Abhaengigkeit), Desktop: `Desktop-App (), Tessera-App · Stand `. Kette: Rust `with_client_marker` haengt neben `desktop=1` die Parameter `dv`/`dc`/`dos` an (drei Aufrufstellen unveraendert, nach In-App-Update automatisch frisch) → Middleware setzt Cookie `tessera_desktop_client` = `||` (musterbereinigt, nur wenn alle drei da) → `getDesktopClientInfo()` → vier optionale DTO-Felder `clientKind/clientOs/clientVersion/clientCommit` (whitelist deklariert, alte Web-Baue/Clients bleiben gueltig) → `describeOrigin()`. Rohe Zeilen `Browser:`/`Fenster:` bleiben; Kuerzel auch in der einen Protokollzeile; nichts in DB, `main.ts` unangetastet (T-GZA-01..04). Tests: API 1124 (origin 10 neu), Web 447, Rust 37, Typecheck sauber. Plan-Pruefer und Verifier bestanden (9/9 must_haves). **Nachweise lokal (mailhog):** Browser → `[Browser] … Herkunft: Browser — Chrome 154 auf Linux`; Desktop-Marker wie der Rust-Client (`?desktop=1&dv=1.2.0&dc=a6d1a64&dos=windows`) → Cookie `1.2.0%7Ca6d1a64%7Cwindows`, `[Desktop/Windows] … Herkunft: Desktop-App (Windows), Tessera-App 1.2.0 · Stand a6d1a64`. **Offen:** Windows-VM-Probe mit echtem Client nach CI-Bau und alpha-Deploy durch den User. | 2026-09-18 | 7169472,b03cb21,f245711,e2a7946 | [260918-gza-fehlermeldung-herkunft-ausweisen-browser](./quick/260918-gza-fehlermeldung-herkunft-ausweisen-browser/) | | fast | **Desktop-Client: Setup-Seite zeigt Version und Stand der App** („Tessera-App 1.2.0 · Stand a6d1a64"; ohne Stempel nur Version) — Command `get_client_info`, Helfer `client_info_label` (2 Tests), `

` in setup.html, CHANGELOG. Diente zugleich als zweiter Desktop-Stand fuer den Update-Nachweis. 35 Rust-Tests. | 2026-09-18 | a6d1a64 | — | | 260921-9ie | **Biome lauffaehig machen und das Lint-Tor scharf schalten (WINDOWS #35).** `biome.json` per `biome migrate` auf Biome 2.5.0 gezogen: `organizeImports` nach `assist.actions.source`, `linter.rules.recommended` → `preset: "recommended"`, `javascript.parser.unsafeParameterDecoratorsEnabled` (NestJS-Parameter-Dekoratoren: 238 parse-Fehler in 19 Dateien → 0), `quoteStyle: single` (belegt: 1496 einfach-gequotete Importzeilen gegen null doppelte), `vcs.useIgnoreFile`, Ausschluss von `**/__fixtures__/**` (nur html/zip/xml, keine TS-Datei) und `globals.css` (Tailwind-4-At-Regeln). `lint`-Skript (`biome lint .`) in allen fuenf Workspaces; `turbo.json` bekommt `globalDependencies: ["biome.json"]`, sonst liefert der Cache nach einer Regelaenderung alte Ergebnisse. **Zweig (b) gewaehlt, gemessen:** `biome check .` → Exit 1/760 Fehler, `biome lint .` → Exit 1/275, also kein "nur Warnungen"-Ausweg; Skript ruft `lint` statt `check` (haelt 319 Formatierungsbefunde draussen, kein Rundumumbau), Rest gezielt auf `warn` → 0 Fehler, Exit 0. **Sicherheit:** Gruppe `security` bleibt auf `error`, maschinell geprueft; die 6 `noScriptUrl`-Treffer lagen ausnahmslos in der ausgeschlossenen HTML-Testvorlage, keiner in echtem Quellcode. **Registereintrag #35 war in zwei Punkten falsch:** Pfad ist `apps/api/src/user/...` (Einzahl), und die Wirkung des Parser-Schalters betrug 238 statt 17 Fehler. **Nachweise (dreifach unabhaengig — Planer, Orchestrator, Verifier):** `pnpm lint` → "5 successful, 5 total", Exit 0 (vorher "No tasks were executed"); Gegenprobe mit Wegwerfdatei (`debugger`) → Exit 1 mit `noDebugger`, danach Baum wieder sauber; repo-weit 0 parse-Fehler, 0 Fehler; Diff nur Konfiguration/Skripte/Doku, keine Quelldatei, `pnpm-lock.yaml` unveraendert. Verifikation passed (7/7). **Offen als eigener Durchlauf:** rund 2800 Warnungen (`any`-Familie, Barrierefreiheit in `apps/web`), in `docs/anleitung-entwicklung.md` als bewusster Rueckstand festgehalten. | 2026-09-21 | 6a727e9,00d769b,6f0f05a | [260921-9ie-windows-35-biome-json-fuer-biome-2-5-0-r](./quick/260921-9ie-windows-35-biome-json-fuer-biome-2-5-0-r/) | +| 260921-a1d | **Benutzerverwaltung: verbotene Aktionen melden sich jetzt (WINDOWS #36).** Drei Stellen in `apps/web/src/app/(portal)/admin/users/page.tsx` verschluckten Server-Antworten still (`if (res.ok)` ohne else, `catch {}` mit dem Kommentar `// silently fail`): Liste laden, Formular speichern, Loeschen. Sichtbare Wirkung vorher: Formular blieb offen, Loeschdialog stand still, beim gescheiterten Laden log die Seite mit "Keine Benutzer gefunden". Jetzt je ein Banner (`role="alert"`) im Listenkopf, im Formulardialog und im Loeschdialog; `readApiMessage(res)` liest ausschliesslich `body.message` und zeigt den Servertext in einem deutschen Rahmensatz, sonst eine uebersetzte Ersatzmeldung — auch wenn der `fetch` selbst wirft. Vier Schluessel `admin.users.errors.*` in `de.json` **und** `en.json` (Katalog-Paritaet 890/890 geprueft). Dazu `canManageRow`: einem ADMIN werden Bearbeiten/Loeschen in der SUPER_ADMIN-Zeile gar nicht erst angeboten (seit #29 im Alltag erreichbar), "Details" bleibt ueberall. **`apps/api` blieb unangetastet** — der Zielrollen-Riegel im Controller ist und bleibt die wirksame Grenze, der versteckte Knopf ist Ergonomie darueber, kein Ersatz; eigenes Gatter im Plan weist das nach. Gemessen: keine Namen/IDs/Stapelspuren in den 403-Rumpftexten (kein eigener ExceptionFilter in `apps/api/src`). **Nachweise (dreifach unabhaengig):** Web-Tests 66 Dateien/459 Tests gruen (vorher 65/447, neue `users-page.test.tsx` prueft echten DOM-Text via `getByText`/`within`, nicht nur State-Setter), type-check Exit 0, `pnpm lint` 5/5 ohne neue Fehlerrang-Meldung, `silently fail` im Code 3 → 0, `role="alert"` 0 → 3, Diff nur vier Dateien unter `apps/web`. Verifikation passed (7/7). | 2026-09-21 | 38d2586,51bff75,13b70df | [260921-a1d-windows-36-benutzerverwaltung-zeigt-bei-](./quick/260921-a1d-windows-36-benutzerverwaltung-zeigt-bei-/) | ## Deferred Items @@ -490,4 +491,4 @@ Last session: 2026-09-21T04:50:00Z Resumed: 2026-09-21 — Sitzung ueber /gsd-resume-work fortgesetzt. Stand geprueft: Arbeitsbaum sauber, main == origin/main auf 55aa287, CI-Lauf 387 fuer 55aa287 erfolgreich (Beta-Images gebaut). Push und CI aus dem letzten Stopp-Punkt sind damit erledigt. Stopped at: Warte auf Nutzerentscheidung, womit weitergearbeitet wird. Offen fuer den User: alpha pullen (web+api) und danach am Windows-VM-Client die echte Fehlermeldung schicken (Betreff `[Desktop/Windows]` + `Herkunft:`-Zeile pruefen); eigenen Arbeitsplatz-Client einmal per Browser-Installer erneuern; Freigabe 1.3.0 auf Zuruf. Technisch offen im Ledger: WINDOWS #35 (Biome laeuft nicht — biome.json:3 `organizeImports` ist in Biome 2.5.0 unbekannt, `biome check` bricht mit Konfigurationsfehler ab, reproduziert 2026-09-21) und WINDOWS #36 (403-Antworten bleiben in handleSubmit/handleDelete ohne sichtbare Reaktion). Resume file: None -Last activity: 2026-09-21 - Quick 260921-9ie (WINDOWS #35): Biome 2.5.0 lauffaehig, `pnpm lint` prueft echt in allen fuenf Workspaces — gruen auf dem Bestand (Exit 0), rot bei echtem Verstoss (Exit 1); Sicherheitsregeln unangetastet, kein Quellcode umformatiert +Last activity: 2026-09-21 - Quick 260921-a1d (WINDOWS #36): Benutzerverwaltung meldet abgewiesene Server-Antworten sichtbar (drei Stellen), ADMIN bekommt in der SUPER_ADMIN-Zeile keine Aktionsknoepfe mehr; apps/api unangetastet, Web-Tests 66/459 gruen diff --git a/.planning/WINDOWS.md b/.planning/WINDOWS.md index 2892def..a6cad7f 100644 --- a/.planning/WINDOWS.md +++ b/.planning/WINDOWS.md @@ -1,10 +1,10 @@ --- schema_version: 1 -open_count: 14 +open_count: 13 waived_count: 1 -fixed_count: 24 +fixed_count: 25 total_count: 39 -last_updated: 2026-09-21T05:06:47.012Z +last_updated: 2026-09-21T05:40:29.821Z --- # Broken Windows Ledger @@ -50,7 +50,7 @@ last_updated: 2026-09-21T05:06:47.012Z | 33 | quick-260911-mkj | unmet-truth | apps/api/src/tenders/tenders.seed.ts | | Modellaufrufe auf Empfaengern, die weder this.prisma noch eine const X = forTenant(-Zuweisung noch ein Transaktionsparameter sind, sind fuer ALLE vier Erkennungsformen der Bestandsaufnahme unsichtbar. Gemessen 260911-mkj: tenders/tenders.seed.ts (Funktionsparameter prisma: PrismaService, tenderRssFeedSource.findFirst/create, kein Eintrag in der Bestandsaufnahme) und tenders/backfill-tender-source.ts (eigenstaendiges Skript mit new PrismaClient(), tender.findMany/update, durch RELATION_SPEC_EXCEPTIONS laut gehalten). Beide beruehren nur den plattformglobalen Katalog bzw. die plattformweite RSS-Verwaltung (WINDOWS #24), heute ungefaehrlich; OFFEN ist der Mechanismus (ein kuenftiger Dienst mit Parameter-Empfaenger auf einer geschuetzten Tabelle bliebe unsichtbar). Zu schliessen vor Etappe 4 durch eine Zaehlung ALLER ..(-Anker gegen die bekannte Empfaengermenge, Ueberschuss laut. | open | | 2026-09-11T14:48:09.723Z | | | 34 | quick-260911-nke | deviation | apps/api/src/prisma/prisma-tenant.extension.ts | | Etappe 3b: ein Nutzer-CRUD-Aufrufer, der den Benutzer an forTenant() vergisst, sieht den ganzen Mandanten (IS-NULL-Form) — gleicher Stand wie vor 20260911120000, keine Verschlechterung, aber kein Netz. Die Bestandsaufnahme unterscheidet nur mandanten-gebunden/ungebunden, nicht benutzer-gebunden; ein Waechter, der jede Methode mit userId-Parameter auf das dritte Argument prueft, ist NICHT gebaut. Bis dahin sind die dreistelligen Spec-Zusicherungen je Dienst das einzige Netz. Vor dem Scharfschalten (Etappe 4, rls-preflight.mjs) zu entscheiden: Waechter bauen oder Rest benennen. | open | | 2026-09-11T15:46:08.295Z | | | 35 | quick-260914-ebg | deviation | biome.json | | Biome ist im Bestand nicht lauffaehig: biome.json traegt den in Biome 2.5.0 unbekannten Schluessel organizeImports (gehoert unter assist), Biome bricht bei jedem Aufruf mit Konfigurationsfehler ab; zusaetzlich fehlt javascript.parser.unsafeParameterDecoratorsEnabled, ohne den jeder NestJS-Parameter-Dekorator ein Parse-Fehler ist (17 allein in user.controller.ts). Der CI-Schritt Lint ruft pnpm lint = turbo lint, keine App hat ein lint-Skript - der Schritt ist ein Leerlauf, der gruen meldet. CLAUDE.md und docs/anleitung-entwicklung.md beschreiben Biome als aktives Werkzeug. Gemessen 260914-ebg; das dortige Gate lief mit einer Ersatzkonfiguration im Scratchpad, relativ zur Baseline (0 Fehler, Warnungen je Datei 22/25/20, alle noExplicitAny-Familie; biome format ebenfalls unsauber, Anfuehrungszeichen-Stil). Zu entscheiden: biome.json reparieren (organizeImports nach assist, Parser-Schalter, quoteStyle single) und ein lint-Skript je App anlegen, dann die Warnungen in einem eigenen Durchlauf abbauen oder als Regelabschaltung begruenden. | fixed | | 2026-09-14T08:38:04.079Z | 2026-09-21T05:06:47.012Z | -| 36 | quick-260914-ebg | deviation | apps/web/src/app/(portal)/admin/users/page.tsx | | handleSubmit und handleDelete pruefen nur res.ok ohne else-Zweig und fangen mit leerem catch - ein 403 der API fuehrt zu keiner sichtbaren Reaktion (Formular bleibt offen, Loeschdialog bleibt stehen, keine Meldung). Bestehendes Verhalten fuer alle 403-Wege (fremder Mandant, Selbstloeschung); seit 260914-ebg (WINDOWS #29) ist der Fall fuer einen ADMIN im Alltag erreichbar, weil die SUPER_ADMIN-Zeile in der eigenen Benutzerliste steht und Aendern/Loeschen darauf jetzt 403 liefert. Familie der still verschluckten Antworten (#28, #32). Frontend von 260914-ebg NICHT geaendert (ausserhalb der Erlaubnisliste). Zu schliessen: Fehlermeldung aus dem Antwortrumpf anzeigen und die Aktionsknoepfe fuer SUPER_ADMIN-Zeilen einem ADMIN gar nicht erst anbieten. | open | | 2026-09-14T08:38:12.619Z | | +| 36 | quick-260914-ebg | deviation | apps/web/src/app/(portal)/admin/users/page.tsx | | handleSubmit und handleDelete pruefen nur res.ok ohne else-Zweig und fangen mit leerem catch - ein 403 der API fuehrt zu keiner sichtbaren Reaktion (Formular bleibt offen, Loeschdialog bleibt stehen, keine Meldung). Bestehendes Verhalten fuer alle 403-Wege (fremder Mandant, Selbstloeschung); seit 260914-ebg (WINDOWS #29) ist der Fall fuer einen ADMIN im Alltag erreichbar, weil die SUPER_ADMIN-Zeile in der eigenen Benutzerliste steht und Aendern/Loeschen darauf jetzt 403 liefert. Familie der still verschluckten Antworten (#28, #32). Frontend von 260914-ebg NICHT geaendert (ausserhalb der Erlaubnisliste). Zu schliessen: Fehlermeldung aus dem Antwortrumpf anzeigen und die Aktionsknoepfe fuer SUPER_ADMIN-Zeilen einem ADMIN gar nicht erst anbieten. | fixed | | 2026-09-14T08:38:12.619Z | 2026-09-21T05:40:29.821Z | | 37 | quick-260914-eym | deviation | apps/api/src/dkv/dkv.service.ts | | Der Single-Flight-Riegel processing in DkvService.processInbox ist EIN prozessweites Boolean, nicht je Mandant. Seit 260914-eym laeuft je aktivem Mandanten ein eigener Cron-Auftrag (dkv-inbox-poll:); ueberschneiden sich zwei Ticks verschiedener Mandanten, bricht der zweite still ab (Warnzeile 'already processing') und der Mandant wartet bis zum naechsten Intervall - kein Datenverlust, Verzoegerung; mit EINEM Mandanten unveraendert. Der Tick blieb in 3c laut Auftrag unangetastet (T-EYM-09, accept mit Aufzeichnung). Zu schliessen: Riegel je Mandant (Set) mit Test 'zwei Mandanten gleichzeitig, beide werden bedient'. | open | | 2026-09-14T09:51:24.295Z | | | 38 | quick-260914-m97 | deviation | apps/api/src/bug-reports/dto/bug-report.dto.ts | 71 | Rule 1: @Expose() auf errors ergaenzt, damit die @Transform-Normalisierung auch bei ganz fehlendem Multipart-Feld greift (class-transformer transformiert nur vorhandene Schluessel) | fixed | | 2026-09-14T15:04:31.846Z | 2026-09-14T15:17:38.808Z | | 39 | quick-260916-dyv | deviation | apps/web/src/components/dashboard/dashboard-grid.test.tsx | | Test 7 pinnt Identitaets-Kopie per toMatchObject statt toEqual (cloneLayoutItem normalisiert moved/static) | fixed | | 2026-09-16T08:48:45.783Z | 2026-09-16T09:00:26.845Z | @@ -485,10 +485,10 @@ last_updated: 2026-09-21T05:06:47.012Z "file": "apps/web/src/app/(portal)/admin/users/page.tsx", "line": null, "description": "handleSubmit und handleDelete pruefen nur res.ok ohne else-Zweig und fangen mit leerem catch - ein 403 der API fuehrt zu keiner sichtbaren Reaktion (Formular bleibt offen, Loeschdialog bleibt stehen, keine Meldung). Bestehendes Verhalten fuer alle 403-Wege (fremder Mandant, Selbstloeschung); seit 260914-ebg (WINDOWS #29) ist der Fall fuer einen ADMIN im Alltag erreichbar, weil die SUPER_ADMIN-Zeile in der eigenen Benutzerliste steht und Aendern/Loeschen darauf jetzt 403 liefert. Familie der still verschluckten Antworten (#28, #32). Frontend von 260914-ebg NICHT geaendert (ausserhalb der Erlaubnisliste). Zu schliessen: Fehlermeldung aus dem Antwortrumpf anzeigen und die Aktionsknoepfe fuer SUPER_ADMIN-Zeilen einem ADMIN gar nicht erst anbieten.", - "status": "open", + "status": "fixed", "reason": "", "recorded_at": "2026-09-14T08:38:12.619Z", - "resolved_at": null, + "resolved_at": "2026-09-21T05:40:29.821Z", "milestone": "v1.2" }, { diff --git a/.planning/quick/260921-a1d-windows-36-benutzerverwaltung-zeigt-bei-/260921-a1d-SUMMARY.md b/.planning/quick/260921-a1d-windows-36-benutzerverwaltung-zeigt-bei-/260921-a1d-SUMMARY.md new file mode 100644 index 0000000..fe3c9fa --- /dev/null +++ b/.planning/quick/260921-a1d-windows-36-benutzerverwaltung-zeigt-bei-/260921-a1d-SUMMARY.md @@ -0,0 +1,177 @@ +--- +phase: quick-260921-a1d +plan: 01 +subsystem: ui +tags: [next-intl, react, vitest, error-handling, rbac] + +requires: + - phase: quick-260914-ebg + provides: "Zielrollen-Riegel in apps/api/src/user/user.controller.ts (update/remove), der SUPER_ADMIN-Zeilen fuer Nicht-SUPER_ADMIN mit 403 abweist (WINDOWS #29)" +provides: + - "Sichtbare Fehlermeldungen (Servertext oder Ersatzmeldung) im Loeschdialog, im Formulardialog und im Listenkopf der Benutzerverwaltung" + - "canManageRow spiegelt den Zielrollen-Riegel client-seitig — ADMIN sieht in der SUPER_ADMIN-Zeile keine Aktionsknoepfe mehr" + - "readApiMessage-Muster fuer den Antwortrumpf, lokal zu apps/web/src/app/(portal)/admin/users/page.tsx" +affects: [admin-users-page, benutzerverwaltung, error-handling-conventions] + +actuals: + tokens: 7650 + tasks: 3 + commits: 3 + +tech-stack: + added: [] + patterns: + - "readApiMessage(res) liest ausschliesslich res.json().message (String oder String[]), nie res.text() — verhindert, dass eine fremde HTML-Fehlerseite (vorgelagerter Proxy) in die Oberflaeche geraet" + - "Drei-Zustaende-Fehlermuster pro Formular/Dialog: serverRejected (mit Detail) / generic (kein Rumpf) / network (Verbindung gescheitert), alle als role=\"alert\"-Banner mit denselben Klassen wie admin/groups/page.tsx" + - "canManageRow spiegelt eine Server-Autorisierungsbedingung rein als UI-Ergonomie — Kommentar verweist explizit auf die Serverstelle, die die eigentliche Grenze zieht" + +key-files: + created: + - "apps/web/src/app/(portal)/admin/users/users-page.test.tsx" + modified: + - "apps/web/src/app/(portal)/admin/users/page.tsx" + - "apps/web/src/messages/de.json" + - "apps/web/src/messages/en.json" + +key-decisions: + - "D-01 bis D-06 aus dem Plan unveraendert umgesetzt: next-intl in beiden Katalogen, Sie-Form, Servertext hat Vorrang vor Ersatzmeldung, apps/api unangetastet, vorhandene Muster (groups/page.tsx Banner, tender-radar-api.ts Rumpf-Auswertung) wiederverwendet, keine Umformatierung" + - "Servertexte bleiben englisch (D-03/Ausgangslage) — Uebersetzung waere eine apps/api-Aenderung und damit ausserhalb dieses Plans; siehe T-A1D-04 im Plan" + +requirements-completed: [WINDOWS-36] + +coverage: + - id: D1 + description: "Ein mit 403 abgewiesener Loeschvorgang zeigt den Servertext im offenen Loeschdialog; Ersatzmeldungen fuer verwertbaren-losen Rumpf und Verbindungsfehler; Erfolgsfall unveraendert" + requirement: "WINDOWS-36" + verification: + - kind: unit + ref: "apps/web/src/app/(portal)/admin/users/users-page.test.tsx#AdminUsersPage — Loeschweg (WINDOWS #36, Aufgabe 1) (4 Tests)" + status: pass + human_judgment: false + - id: D2 + description: "Abgewiesenes Speichern zeigt den Servertext (inkl. verketteter Pruefmeldungen) im offenen Formular; Netzmeldung bei Verbindungsfehler; gescheitertes Laden meldet sich im Listenkopf statt faelschlich 'Keine Benutzer gefunden' zu zeigen; Meldungen ueberdauern kein Dialog-Schliessen" + requirement: "WINDOWS-36" + verification: + - kind: unit + ref: "apps/web/src/app/(portal)/admin/users/users-page.test.tsx#AdminUsersPage — Formularweg und Listenladen (WINDOWS #36, Aufgabe 2) (5 Tests)" + status: pass + human_judgment: false + - id: D3 + description: "Ein ADMIN bekommt in der SUPER_ADMIN-Zeile weder Bearbeiten noch Loeschen angeboten (Details bleibt); ein SUPER_ADMIN bekommt beide; Selbstloeschungssperre unveraendert" + requirement: "WINDOWS-36" + verification: + - kind: unit + ref: "apps/web/src/app/(portal)/admin/users/users-page.test.tsx#AdminUsersPage — Aktionsknoepfe der SUPER_ADMIN-Zeile (WINDOWS #36, Aufgabe 3) (3 Tests)" + status: pass + human_judgment: false + +duration: 14min +completed: 2026-09-21 +status: complete +--- + +# Quick 260921-a1d: Stille 403-Antworten in der Benutzerverwaltung Summary + +**Loesch-, Formular- und Ladeweg der Benutzerverwaltung melden abgewiesene Serverantworten jetzt sichtbar (Servertext oder uebersetzte Ersatzmeldung); ein ADMIN bekommt an der SUPER_ADMIN-Zeile keine Aktionsknoepfe mehr angeboten.** + +## Performance + +- **Duration:** 14 min +- **Started:** 2026-09-21T07:19:00+02:00 (gemessen: erster Lesevorgang) +- **Completed:** 2026-09-21T07:33:48+02:00 (letzter Task-Commit) +- **Tasks:** 3/3 +- **Files modified:** 4 (1 neu, 3 geaendert) + +## Gemessene Testzahlen + +- **Vorher** (Ausgangsmessung im Plan, 2026-09-21): 65 Dateien, 447 Tests, alle gruen. +- **Nachher** (dieser Lauf, `pnpm --filter @tessera/web test`): **66 Dateien, 459 Tests, alle gruen.** + Die neue Datei `users-page.test.tsx` traegt 20 Tests (4 aus Aufgabe 1, 5 aus Aufgabe 2, 3 aus Aufgabe 3 — + plus die schon vorher zu `admin/users` zaehlende `user-access-modal.test.tsx` mit 8 Tests, macht 12 in der + Datei `admin/users`-Glob-Messung von Aufgabe 1/2/3). + +## Accomplishments + +- `readApiMessage(res)` liest gezielt `body.message` (String oder verkettetes String-Feld) aus einer + Nicht-2xx-Antwort und gibt sonst `null` zurueck — niemals den ganzen Rumpf. +- Drei bisher stumme Fehlerwege melden sich jetzt sichtbar: `handleDelete` (Loeschdialog), + `handleSubmit` (Formulardialog), `fetchUsers` (Listenkopf). Jeder Zweig endet in einer Meldung: + Servertext (`errors.serverRejected`), Ersatzmeldung ohne Rumpf (`errors.generic`), Verbindungsfehler + (`errors.network`) oder Ladefehler (`errors.loadFailed`). +- Bei gescheitertem Laden erscheint nicht mehr faelschlich "Keine Benutzer gefunden" — die Tabelle bleibt + einfach weg, das Banner sagt, was wirklich passiert ist. +- `canManageRow(user)` spiegelt exakt die serverseitige Bedingung + (`user.role === 'SUPER_ADMIN' && currentUser?.role !== 'SUPER_ADMIN'`) aus + `apps/api/src/user/user.controller.ts` (`update`/`remove`) und blendet Bearbeiten/Loeschen in der + SUPER_ADMIN-Zeile fuer jeden Nicht-SUPER_ADMIN komplett aus — nicht nur gesperrt, sondern nicht vorhanden. +- Vier neue Schluessel unter `admin.users.errors` in `de.json` und `en.json`, identischer Schluesselsatz, + Umlaut-Waechter gruen. + +## Task Commits + +Jede Aufgabe wurde atomar committet: + +1. **Aufgabe 1: Loeschweg von der Serverantwort bis zur sichtbaren Meldung** - `38d2586` (fix) +2. **Aufgabe 2: Formularweg und Listenladen auf dieselbe Rueckmeldung heben** - `51bff75` (fix) +3. **Aufgabe 3: Aktionsknoepfe der SUPER_ADMIN-Zeile einem ADMIN nicht anbieten, Gesamtlauf** - `13b70df` (fix) + +**Plan-Basis:** `24f51e9` (Plan bereits committet vor Ausfuehrung) + +## Files Created/Modified + +- `apps/web/src/app/(portal)/admin/users/page.tsx` - `readApiMessage`, drei neue Fehlerzustaende + (`deleteError`, `formError`, `loadError`), drei `role="alert"`-Banner, `canManageRow` fuer die + Sichtbarkeit der Aktionsknoepfe. +- `apps/web/src/app/(portal)/admin/users/users-page.test.tsx` - neu, 20 Tests fuer alle drei Aufgaben. +- `apps/web/src/messages/de.json` / `en.json` - Zweig `admin.users.errors` mit vier Schluesseln je Sprache. + +## Decisions Made + +- Servertexte bleiben englisch und werden ungeaendert im deutschen Rahmensatz angezeigt (D-03 aus dem + Plan) — eine Uebersetzung waere eine `apps/api`-Aenderung und damit ausserhalb dieses Plans. Als + Beobachtung im Plan unter T-A1D-04 vermerkt, hier nicht behandelt. +- `readApiMessage` ist bewusst lokal in `page.tsx` kopiert statt aus `apps/web/src/lib/tender-radar-api.ts` + importiert — jene Datei gehoert zum Modul Ausschreibungs-Radar, die Verwaltungsseite soll nicht davon + abhaengen (Formvorlage `extractErrorMessage`, D-05). +- `fetchUsers` (dritte Fundstelle, urspruenglich nicht Teil der Fehlermeldung) wurde bewusst mitbehandelt, + wie der Plan es verlangt — nichts wurde aus Bequemlichkeit ausgelassen. + +## Deviations from Plan + +None - plan executed exactly as written. + +## Issues Encountered + +None. + +## User Setup Required + +None - keine externe Dienstkonfiguration erforderlich. + +## Verifikation (aus der ``-Sektion des Plans) + +1. `pnpm --filter @tessera/web test` → **66 Testdateien, 459 Tests, 0 Fehler** (Ausgangslage: 65/447). +2. `pnpm --filter @tessera/web type-check` → **Exit 0**. +3. `pnpm lint` → **5 von 5 Workspaces erfolgreich** (287 bestehende Warnungen, 0 neue Fehlerrang-Meldungen). +4. `git status --porcelain` → ausschliesslich Pfade unter `apps/web/src/`, kein Pfad unter `apps/api/`. + +Von Hand vorgesehene Sichtproben (Sichtprobe a: SUPER_ADMIN-Zeile zeigt nur "Details" fuer einen ADMIN; +Sichtprobe b: Platzierung des roten Banners) wurden **nicht** durchgefuehrt — der Plan bezeichnet sie +ausdruecklich als optional, da die automatisierten Tests dasselbe Verhalten bereits abdecken. + +## Next Phase Readiness + +- WINDOWS #36 ist inhaltlich geschlossen — dieser Lauf schliesst den beschriebenen Fehler vollstaendig + (alle drei stillen Wege sowie die Sichtbarkeit der Aktionsknoepfe). Die Ledger-Eintragung selbst + (`gsd-tools windows fixed 36`) erfolgt durch den Orchestrator, nicht durch diesen Ausfuehrungslauf. +- WINDOWS #28 und #32 derselben Fehlerfamilie bleiben ausdruecklich offen — nicht Teil dieses Plans. +- Keine Blocker fuer nachfolgende Arbeit an der Benutzerverwaltung. + +--- +*Phase: quick-260921-a1d* +*Completed: 2026-09-21* + +## Self-Check: PASSED + +Alle vier veraenderten Dateien und die neue Testdatei existieren auf der Platte; alle drei +Task-Commits (`38d2586`, `51bff75`, `13b70df`) sind in der Git-Historie auffindbar. diff --git a/.planning/quick/260921-a1d-windows-36-benutzerverwaltung-zeigt-bei-/260921-a1d-VERIFICATION.md b/.planning/quick/260921-a1d-windows-36-benutzerverwaltung-zeigt-bei-/260921-a1d-VERIFICATION.md new file mode 100644 index 0000000..e128b4c --- /dev/null +++ b/.planning/quick/260921-a1d-windows-36-benutzerverwaltung-zeigt-bei-/260921-a1d-VERIFICATION.md @@ -0,0 +1,101 @@ +--- +phase: quick-260921-a1d +verified: 2026-09-21T05:39:19Z +status: passed +score: 7/7 must-haves verified +covered_files: + - .planning/quick/260921-a1d-windows-36-benutzerverwaltung-zeigt-bei-/260921-a1d-PLAN.md + - .planning/quick/260921-a1d-windows-36-benutzerverwaltung-zeigt-bei-/260921-a1d-SUMMARY.md + - "apps/web/src/app/(portal)/admin/users/page.tsx" + - "apps/web/src/app/(portal)/admin/users/users-page.test.tsx" + - apps/web/src/messages/de.json + - apps/web/src/messages/en.json +behavior_unverified: 0 +overrides_applied: 0 +--- + +# Quick 260921-a1d: WINDOWS #36 (stille 403-Antworten) Verification Report + +**Task-Ziel:** Beide Haelften von WINDOWS #36 schliessen — (a) jede nicht-ok Serverantwort erzeugt eine +sichtbare Meldung aus dem API-Rumpf, (b) ein ADMIN bekommt in der SUPER_ADMIN-Zeile keine Bearbeiten-/ +Loeschen-Knoepfe angeboten. + +**Verified:** 2026-09-21T05:39:19Z +**Status:** passed + +## Goal Achievement + +### Observable Truths + +| # | Truth | Status | Evidence | +|---|-------|--------|----------| +| 1 | 403 beim Speichern zeigt sichtbare Meldung im offenen Formular mit Servertext | ✓ VERIFIED | `page.tsx:164-176` (`handleSubmit` Sonst-/Fang-Zweig, `formError`-Banner Z. 437-444); Test "zeigt den Servertext im offenen Formular..." (Z. 323-361) prueft `screen.getByText(...)` auf den tatsaechlichen Rahmensatz UND dass das Feld Benutzername weiterhin im Dokument steht | +| 2 | 403 beim Loeschen zeigt sichtbare Meldung im offenen Loeschdialog | ✓ VERIFIED | `page.tsx:178-197` (`handleDelete`, `deleteError`-Banner Z. 472-479); Test Z. 167-208 prueft Servertext UND dass der Bestaetigungstext weiterhin im Dokument steht | +| 3 | Unverwertbarer Rumpf oder Verbindungsfehler zeigen uebersetzte Ersatzmeldung, nie leere Reaktion | ✓ VERIFIED | `readApiMessage` (Z. 39-50) gibt bei Ausnahme/leerem Feld `null` zurueck, Aufrufer faellt auf `t('errors.generic')`; Fang-Zweige setzen `t('errors.network')`. Tests Z. 210-244 (Loeschen), 402-433 (Formular) pruefen exakt diese Texte im DOM | +| 4 | Gescheitertes Laden der Liste meldet sich, zeigt nicht mehr faelschlich "Keine Benutzer gefunden" | ✓ VERIFIED | `fetchUsers` (Z. 83-99) setzt `loadError`; Rendern Z. 251 unterdrueckt "noUsers", wenn `loadError` gesetzt ist. Test Z. 435-457 prueft beides per `getByText`/`queryByText` | +| 5 | ADMIN sieht in SUPER_ADMIN-Zeile weder Bearbeiten noch Loeschen; SUPER_ADMIN sieht beide | ✓ VERIFIED | `canManageRow` (Z. 212-213) spiegelt exakt die Serverbedingung; Knopfreihe Z. 316-335 rendert Bearbeiten/Loeschen nur bei `canManageRow(user)`. Tests Z. 516-549 pruefen beide Rollen per `within(row)` | +| 6 | Alle neuen Texte in de.json UND en.json, identischer Schluesselsatz, kein fest verdrahteter Text | ✓ VERIFIED | Vier Schluessel unter `admin.users.errors` in beiden Dateien identisch; volle Katalogpruefung: 890 Schluessel je Sprache, 0 Abweichung (siehe Data-Flow-Trace); `grep` auf die deutschen Fehlertexte im TSX findet nichts — alles laeuft ueber `t(...)` | +| 7 | apps/api unveraendert — ausgeblendete Knoepfe sind Ergonomie, kein Ersatz fuer die Serverpruefung | ✓ VERIFIED | `git diff --name-only 24f51e9..13b70df` zeigt ausschliesslich `apps/web`-Pfade; `apps/api/src/user/user.controller.ts` Z. 196-205/258-264 traegt den unveraenderten Zielrollen-Riegel, dessen Bedingung `canManageRow` client-seitig spiegelt | + +**Score:** 7/7 truths verified (0 present, behavior-unverified) + +### Required Artifacts + +| Artifact | Expected | Status | Details | +|----------|----------|--------|---------| +| `apps/web/src/app/(portal)/admin/users/page.tsx` | drei Fehlerzustaende, drei Meldungsflaechen, Rollenfilter | ✓ VERIFIED | `deleteError`/`formError`/`loadError`, drei `role="alert"`-Stellen (Z. 241, 439, 474), `canManageRow` (Z. 212-213) und dessen Anwendung (Z. 316, 324) | +| `apps/web/src/app/(portal)/admin/users/users-page.test.tsx` | neue Vitest-Datei mit Verhaltensnachweisen | ✓ VERIFIED | Neue Datei, 12 Tests (4+5+3), alle pruefen gerenderten Text via `screen.getByText`/`within`, nicht nur State | +| `apps/web/src/messages/de.json` / `en.json` | Zweig `admin.users.errors` mit vier Schluesseln je Sprache | ✓ VERIFIED | `serverRejected`, `generic`, `network`, `loadFailed` identisch in beiden Dateien | + +### Key Link Verification + +| From | To | Via | Status | Details | +|------|-----|-----|--------|---------| +| `readApiMessage(res)` | `t('errors.serverRejected', { detail })` -> sichtbares Banner | direkter Aufruf im Sonst-Zweig von `handleSubmit`/`handleDelete` | WIRED | Code Z. 164-176, 185-197; durch Tests Z. 167-208, 323-361 mit tatsaechlichem DOM-Text bestaetigt | +| `currentUser.role` (auth-store) | Sichtbarkeit der Aktionsknoepfe je Zeile | `canManageRow(user)` nutzt `currentUser?.role` | WIRED | Code Z. 60, 212-213, 316, 324; Tests Z. 516-549 mit zwei Rollen | +| `de.json`/`en.json` Schluesselgleichheit | `umlaut-guard.spec.ts` | Test laeuft ueber gesamten Katalog | WIRED | `pnpm --filter @tessera/web test -- --run messages/umlaut-guard` -> 3 Tests gruen; zusaetzlich eigene Node-Pruefung: 890/890 Schluessel identisch | + +### Behavioral Spot-Checks + +| Behavior | Command | Result | Status | +|----------|---------|--------|--------| +| Testbestand admin/users (neu+bestehend) | `pnpm --filter @tessera/web test -- --run 'admin/users'` | 2 Dateien, 20 Tests, alle gruen | ✓ PASS | +| Gesamter Web-Testbestand (einmalig, voller Lauf) | `pnpm --filter @tessera/web test -- --run` | 66 Dateien, 459 Tests, alle gruen (Ausgangslage 65/447) | ✓ PASS | +| Umlaut-Waechter | `pnpm --filter @tessera/web test -- --run 'messages/umlaut-guard'` | 3 Tests gruen | ✓ PASS | +| Typpruefung `apps/web` | `pnpm --filter @tessera/web type-check` | Exit 0 | ✓ PASS | +| Lint, ganzes Monorepo (frisch, ohne Cache) | `pnpm lint --force` | 5 von 5 Workspaces erfolgreich, keine neue Fehlerrang-Meldung | ✓ PASS | +| Lint gezielt auf die zwei geaenderten Dateien | `biome lint page.tsx users-page.test.tsx` | 12 Warnungen (a11y/useButtonType, noExplicitAny — bestehende Muster), 0 Fehler | ✓ PASS | +| `apps/api` unveraendert | `git diff --name-only 24f51e9 13b70df` | Nur `apps/web`-Pfade | ✓ PASS | +| Lockfile/Version unveraendert | `git diff --stat 24f51e9 13b70df -- pnpm-lock.yaml package.json apps/web/package.json` | keine Ausgabe (kein Diff) | ✓ PASS | +| Debt-Marker / dangerouslySetInnerHTML | `grep -n -E "TBD\|FIXME\|XXX\|TODO\|HACK\|PLACEHOLDER"` und `dangerouslySetInnerHTML` auf beiden Dateien | keine Treffer | ✓ PASS | +| Kein fest verdrahteter Fehlertext im TSX | `grep` auf die deutschen Fehlerformulierungen in `page.tsx` | keine Treffer (alles ueber `t(...)`) | ✓ PASS | + +### Anti-Patterns Found + +Keine. Kein Debt-Marker, kein `dangerouslySetInnerHTML`, keine fest verdrahtete Zeichenkette, keine +Umformatierung (Diffstats 42/38/44 Zeilen je Commit in `page.tsx`, keine Ganzdatei-Rewrites), kein +Abhaengigkeits-/Lockfile-Wechsel. + +### Requirements Coverage + +| Requirement | Beschreibung | Status | Evidence | +|-------------|-------------|--------|----------| +| WINDOWS-36 | Benutzerverwaltung zeigt bei 403 keine Rueckmeldung; SUPER_ADMIN-Zeile bietet ADMIN keine Aktionsknoepfe | ✓ SATISFIED | Alle 7 Truths oben verifiziert; Ledger-Eintragung (`gsd-tools windows fixed 36`) laut SUMMARY bewusst dem Orchestrator ueberlassen, kein Teil dieses Ausfuehrungslaufs | + +### Human Verification Required + +Keine. Die im Plan vorgesehenen manuellen Sichtproben sind ausdruecklich als optional markiert ("die Tests +decken das Verhalten bereits ab") und durch die automatisierten DOM-Assertions (nicht nur State-Pruefungen) +tatsaechlich abgedeckt. + +### Gaps Summary + +Keine Luecken gefunden. Alle sieben Must-Have-Truths, alle drei Artefakte und alle drei Key-Links sind +verifiziert; die Testzahlen (66 Dateien / 459 Tests), die Typpruefung (Exit 0) und der Lint-Lauf (5/5, keine +neue Fehlermeldung) decken sich mit den SUMMARY-Angaben und wurden unabhaengig nachvollzogen. `apps/api` ist +nachweislich unveraendert, der Zielrollen-Riegel im Controller bleibt die alleinige wirksame Grenze. + +--- + +_Verified: 2026-09-21T05:39:19Z_ +_Verifier: Claude (gsd-verifier)_