--- 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.