Files
tessera-ctl/.planning/phases/16-ad-gruppen-synchronisation/16-04-SUMMARY.md
T

201 lines
15 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
phase: 16-ad-gruppen-synchronisation
plan: 04
subsystem: ui
tags: [nextjs, next-intl, groups, module-grants, vitest, tailwind]
# Dependency graph
requires:
- phase: 16-ad-gruppen-synchronisation (Plan 16-02)
provides: "GroupsService.update() serverseitige Namenssperre fuer importierte Gruppen (BadRequestException), internalName-Feld mit Leerstring-Normalisierung auf null, UpdateGroupDto ohne ldapDn (D-07), internalName in GET /groups"
provides:
- "GroupFormModal.tsx auf drei Zustaende zurueckgebaut (Anlegen / lokal bearbeiten / importiert bearbeiten) — kein AD-Bindungspfad mehr in der Oberflaeche (D-07)"
- "Gesperrtes Namensfeld mit Herkunftshinweis + AD-DN-Anzeigezeile und editierbarem internen Namen fuer importierte Gruppen (D-03, D-04)"
- "Sichtbarer, ursachenunterscheidender Speicherfehler im Dialog (409 vs. generisch) statt stummem catch{}"
- "Group.internalName im Frontend-Typ; Gruppenliste zeigt internalName ?? name mit AD-Name als Tooltip (D-04)"
- "Sechs verwaiste admin.groups.ldapBind.*-Schluessel aus de.json/en.json entfernt, Schluesselparitaet erhalten"
affects: [16-05-anzeige-fallback-frontend]
# Actuals (#2632)
actuals:
tokens: 5732
tasks: 3
commits: 3
# Tech tracking
tech-stack:
added: []
patterns:
- "isImported = group?.ldapDn != null als alleiniges Unterscheidungsmerkmal lokal vs. importiert im Dialog (nicht der noch nicht in jedem Fall nachgetragene ldapObjectGuid-Identitaetsschluessel)"
- "Ein einziger method: 'PATCH'-Aufrufblock bedient sowohl den lokalen Rename- als auch den internalName-Pfad, Payload wird vorher per isImported-Verzweigung gebaut — verhindert einen zweiten Schreibpfad im Bearbeiten-Zustand"
key-files:
created: []
modified:
- apps/web/src/app/(portal)/admin/groups/components/GroupFormModal.tsx
- apps/web/src/app/(portal)/admin/groups/page.tsx
- apps/web/src/app/(portal)/admin/groups/groups-page.test.tsx
- apps/web/src/messages/de.json
- apps/web/src/messages/en.json
key-decisions:
- "Group.internalName wurde bereits in Task 1 zum Interface in page.tsx ergaenzt (Rule 3, Blocker: GroupFormModal.tsx kompiliert sonst nicht), obwohl die Erweiterung explizit als Task-2-Auftrag im Plan stand — Task 2 fand das Feld vor und konzentrierte sich auf die Namenszelle plus Tests"
- "Der verwaiste ldapBind-Uebersetzungsblock im next-intl-Mock der Testdatei wurde zusaetzlich zu den beiden Sprachdateien entfernt (nicht explizit im Plan gefordert, aber tote Kopie desselben Schluesselbaums) — ersetzt durch die sieben neuen Task-1-Schluessel im selben Mock"
- "PERM-02 bleibt unveraendert auf Pending — dieser Plan schliesst laut eigener Vorgabe erst mit Plan 16-05"
patterns-established:
- "Sichtbarer text-sm text-destructive-Fehlerzustand im GroupFormModal fuer JEDEN Fehlerfall (Netzwerk, 409, generisch 4xx/5xx), kein stummer catch{} mehr in dieser Komponente"
requirements-completed: [] # PERM-02 bleibt bewusst offen bis 16-05, siehe key-decisions
coverage:
- id: D1
description: "GroupFormModal bietet in keinem der drei Zustaende einen Weg, eine lokale Gruppe an eine AD-Gruppe zu binden — kein Radio-Steuerelement, kein Discovery-Aufruf, kein zweiter PATCH im Anlegen-Zweig (D-07)"
requirement: "PERM-02"
verification:
- kind: unit
ref: "apps/web/src/app/(portal)/admin/groups/groups-page.test.tsx#GroupFormModal (via AdminGroupsPage) — D-07: no AD-binding path (2 Faelle)"
status: pass
- kind: other
ref: "grep -c 'ldap/groups' GroupFormModal.tsx == 0; grep -c 'type=\"radio\"' GroupFormModal.tsx == 0; grep -c \"method: 'PATCH'\" GroupFormModal.tsx == 1"
status: pass
human_judgment: false
- id: D2
description: "Namensfeld einer importierten Gruppe ist deaktiviert, zeigt den AD-Namen, traegt Herkunftshinweis und AD-DN-Anzeigezeile; internalName ist separat editierbar und wird ausschliesslich per PATCH { internalName } gespeichert (D-03, D-04)"
requirement: "PERM-02"
verification:
- kind: unit
ref: "apps/web/src/app/(portal)/admin/groups/groups-page.test.tsx#GroupFormModal (via AdminGroupsPage) — locks the name field and shows an editable internal-name field for an imported group"
status: pass
human_judgment: false
- id: D3
description: "Speicherfehler wird im Dialog sichtbar angezeigt (nicht stumm verschluckt), Dialog bleibt offen, Eingaben bleiben erhalten; 409 zeigt saveErrorNameTaken statt saveError"
requirement: "PERM-02"
verification:
- kind: other
ref: "grep -q '409' GroupFormModal.tsx; Code-Review des handleSubmit-Fehlerpfads (kein return vor onClose() im Erfolgsfall, saveError-State bleibt bei Fehler gesetzt, Formular wird nicht zurueckgesetzt)"
status: pass
human_judgment: true
rationale: "Kein automatisierter Test simuliert den 409-Response-Pfad oder das Offenbleiben des Dialogs nach einem fehlgeschlagenen Speichern in dieser Session — der manuelle Browser-Durchklick aus dem Plan-Verification-Block ist als WINDOWS.md #5 offen und deckt diesen Zustand mit ab."
- id: D4
description: "Gruppenliste zeigt internalName ?? name (nullish, nicht truthiness) mit AD-Namen als title-Tooltip; Badge-Spalte unveraendert als alleiniger importiert-Marker"
requirement: "PERM-02"
verification:
- kind: unit
ref: "apps/web/src/app/(portal)/admin/groups/groups-page.test.tsx#AdminGroupsPage — shows the internal name.../falls back to the stored name... (2 Faelle)"
status: pass
- kind: other
ref: "grep -c 'internalName ?? group.name' page.tsx == 1; grep -c 'internalName ||' page.tsx == 0; grep -c 'boundBadge' page.tsx == 1"
status: pass
human_judgment: false
- id: D5
description: "Sechs verwaiste admin.groups.ldapBind.*-Schluessel sind aus de.json UND en.json entfernt; kein Quelltext in apps/web referenziert sie noch; beide Sprachdateien tragen unter admin.groups denselben Schluesselsatz"
requirement: "PERM-02"
verification:
- kind: other
ref: "grep -c '\"ldapBind\"' de.json/en.json == 0; grep -rc 'ldapBind\\.' apps/web/src --include=*.tsx --include=*.ts == 0; node-Schluesselparitaets-Check admin.groups de vs. en"
status: pass
human_judgment: false
# Metrics
duration: 3min (Commit-Spanne 16:28:07 bis 16:30:49; Kontext-Lesen davor nicht mitgerechnet)
completed: 2026-08-06
status: complete
---
# Phase 16 Plan 4: Gruppen-Dialog auf Anlegen/Umbenennen/Import-Herkunft zurueckgebaut Summary
**`GroupFormModal.tsx` verliert restlos die AD-Radio-Auswahl und den zweistufigen Anlegen-dann-Binden-Ablauf (D-07); an ihre Stelle tritt ein gesperrtes Namensfeld mit sichtbarer AD-Herkunft plus editierbarem internem Namen fuer importierte Gruppen (D-03/D-04), ein sichtbarer statt stummer Speicherfehler mit Namenskollisions-Unterscheidung, und eine Gruppenliste, die den internen Namen mit AD-Name-Tooltip zeigt.**
## Performance
- **Duration:** 3 min (Commit-Spanne 30affbb 16:28:07 Uhr bis e900b43 16:30:49 Uhr; Lesen von PLAN.md, 16-02-SUMMARY.md, PATTERNS.md, UI-SPEC.md und der Zieldateien davor nicht mitgerechnet)
- **Started:** 2026-08-06T16:28:07+02:00 (erster Task-Commit)
- **Completed:** 2026-08-06T16:30:49+02:00 (dritter Task-Commit)
- **Tasks:** 3
- **Files modified:** 5
## Accomplishments
- `GroupFormModal.tsx` auf drei klar getrennte Zustaende zurueckgebaut (Anlegen / lokal bearbeiten / importiert bearbeiten) — der komplette AD-Bindungsblock (Discovery-State, `useEffect`, Radio-Liste, Bindung-entfernen-Link, zweiter PATCH im Anlegen-Zweig) ist restlos entfernt; ein manipulierter Client kann eine lokale Gruppe ueber diesen Dialog nicht mehr an AD binden (D-07)
- Importierte Gruppen zeigen ein deaktiviertes Namensfeld mit Herkunftshinweis, eine reine `AD-DN:`-Anzeigezeile und ein unabhaengiges, editierbares Feld "Interner Name" — gespeichert wird in diesem Zustand ausschliesslich `{ internalName }` per PATCH, nie `name` (D-03, D-04)
- Ein einziger `method: 'PATCH'`-Aufrufblock bedient sowohl den lokalen Rename- als auch den `internalName`-Pfad — die Payload wird vor dem Aufruf per `isImported`-Verzweigung gebaut, kein zweiter Schreibpfad
- Speicherfehler werden nicht mehr stumm verschluckt: der bisherige `catch {}` ist durch einen sichtbaren `text-sm text-destructive`-Zustand ersetzt, der bei HTTP 409 `saveErrorNameTaken` statt der generischen `saveError`-Meldung zeigt; der Dialog bleibt in jedem Fehlerfall offen, Eingaben bleiben erhalten
- `Group.internalName` im Frontend-Typ ergaenzt; die Namenszelle der Gruppenliste rendert `internalName ?? name` (Nullish, nicht Truthiness) mit dem AD-Namen als `title`-Tooltip — die Badge-Spalte bleibt unveraendert der alleinige "importiert vs. lokal"-Marker
- Sechs verwaiste `admin.groups.ldapBind.*`-Schluessel aus `de.json` UND `en.json` entfernt, inklusive des toten Duplikats im Test-Uebersetzungs-Mock; Schluesselparitaet zwischen beiden Sprachen bestaetigt per Node-Check
- Vollstaendige Web-Testsuite gruen: 32 Testdateien, 192 Tests (davon 4 neue Faelle aus diesem Plan)
## Task Commits
Each task was committed atomically:
1. **Task 1: GroupFormModal auf drei Zustaende zurueckbauen (D-03, D-04, D-07)** - `30affbb` (feat)
2. **Task 2: Namensanzeige mit Fallback in der Gruppenliste (D-04)** - `6802b48` (feat)
3. **Task 3: Verwaiste Uebersetzungsschluessel entfernen und D-07 im Test festnageln** - `e900b43` (test)
**Plan metadata:** wird mit diesem Summary committet
_Note: Kein TDD-Task in diesem Plan (`tdd` nicht auf einem Task gesetzt) — je ein `feat`/`feat`/`test`-Commit._
## Files Created/Modified
- `apps/web/src/app/(portal)/admin/groups/components/GroupFormModal.tsx` - AD-Bindungsblock vollstaendig entfernt; drei Zustaende (Anlegen/lokal/importiert), gesperrtes Namensfeld, `internalName`-Feld, sichtbarer 409-unterscheidender Fehlerzustand
- `apps/web/src/app/(portal)/admin/groups/page.tsx` - `Group.internalName: string | null` im Interface; Namenszelle nutzt `internalName ?? name` mit `title={name}`-Tooltip
- `apps/web/src/app/(portal)/admin/groups/groups-page.test.tsx` - `internalName` im lokalen `Group`-Interface und in `mockGroups`; neue `mockGroupsWithInternalName`-Fixture; vier neue Testfaelle (Namensfallback + Tooltip, D-07 kein AD-Request beim Anlegen, gesperrtes Namensfeld + internes Namensfeld beim Bearbeiten einer importierten Gruppe); toter `ldapBind`-Block im Uebersetzungs-Mock durch die sieben neuen Task-1-Schluessel ersetzt
- `apps/web/src/messages/de.json` / `en.json` - sieben neue `admin.groups.*`-Schluessel (`nameLockedHint`, `adDnLabel`, `internalName`, `internalNameHint`, `createLdapHint`, `goToLdap`, `saveErrorNameTaken`); sechs verwaiste `ldapBind.*`-Schluessel entfernt
## Decisions Made
- `Group.internalName` wurde bereits in Task 1 zum `Group`-Interface in `page.tsx` ergaenzt, nicht erst in Task 2 wie im Plan vorgesehen — `GroupFormModal.tsx` referenziert `group.internalName` und kompiliert ohne das Feld nicht (`tsc --noEmit` ist Teil von Task 1s eigenem Verify-Block). Task 2 fand das Feld bereits vor und konzentrierte sich wie geplant auf die Namenszelle und die zugehoerigen Tests.
- Der verwaiste `ldapBind`-Block im next-intl-Uebersetzungs-Mock der Testdatei wurde zusaetzlich zu den beiden echten Sprachdateien entfernt — das war nicht woertlich im Plan gefordert, aber eine tote Kopie desselben Schluesselbaums, deren Entfernung im Sinne von Task 3 liegt (keine verwaisten Schluesselverweise irgendwo im Web-Verzeichnis).
- PERM-02 bleibt in `REQUIREMENTS.md` unveraendert auf `[ ]` (Pending) — dieser Plan liefert den vierten von fuenf geplanten Bausteinen; das Requirement schliesst laut Plan-Vorgabe erst mit Plan 16-05.
## Deviations from Plan
### Auto-fixed Issues
**1. [Rule 3 - Blocking] `Group.internalName` musste vor Task 2 ergaenzt werden, da `GroupFormModal.tsx` sonst nicht kompiliert**
- **Found during:** Task 1 (eigener `tsc --noEmit`-Verify-Schritt)
- **Issue:** `GroupFormModal.tsx` liest `group?.internalName` und `group.internalName` (fuer den AD-DN-Block), aber das `Group`-Interface in `page.tsx` (aus dem der Typ importiert wird) hatte das Feld noch nicht — laut Plan sollte das erst in Task 2 ergaenzt werden. Ohne das Feld schlaegt `tsc --noEmit` bereits fuer Task 1 fehl.
- **Fix:** `internalName: string | null;` zum `Group`-Interface in `page.tsx` als Teil des Task-1-Commits ergaenzt (identische Nullbarkeit wie `ldapDn`, wie vom Plan fuer Task 2 spezifiziert). Task 2 fand das Feld vor und musste es nicht erneut hinzufuegen.
- **Files modified:** apps/web/src/app/(portal)/admin/groups/page.tsx
- **Verification:** `cd apps/web && npx tsc --noEmit` fehlerfrei
- **Committed in:** 30affbb (Task 1)
**2. [Rule 1 - Bug] `t('adDnLabel', { ldapDn: group.ldapDn })` liess sich nicht typsicher aufrufen**
- **Found during:** Task 1 (`tsc --noEmit`)
- **Issue:** `group.ldapDn` ist `string | null`; die `useTranslations`-Interpolationsfunktion erwartet `string | number | Date` fuer Platzhalterwerte. Obwohl `isImported` an dieser Stelle logisch bereits `group.ldapDn !== null` garantiert, engt TypeScript den Typ ueber die separate `isImported`-Variable nicht ein.
- **Fix:** `group.ldapDn ?? ''` an der Aufrufstelle — der Leerstring-Fall tritt zur Laufzeit nie ein, da der Block nur bei `isImported === true` gerendert wird.
- **Files modified:** apps/web/src/app/(portal)/admin/groups/components/GroupFormModal.tsx
- **Verification:** `cd apps/web && npx tsc --noEmit` fehlerfrei
- **Committed in:** 30affbb (Task 1)
---
**Total deviations:** 2 auto-fixed (1× Rule 3 Blocker, 1× Rule 1 Typfehler) — beide innerhalb von Task 1 aufgetreten und behoben, kein Einfluss auf den Funktionsumfang.
**Impact on plan:** Kein Scope Creep. Der Rule-3-Fix zieht lediglich eine im Plan fuer Task 2 vorgesehene Interface-Erweiterung um einen Task vor, weil eine harte Kompilierabhaengigkeit besteht, die der Plan nicht explizit adressiert hatte.
## Issues Encountered
None.
## User Setup Required
None - keine externe Service-Konfiguration erforderlich. Der laufende Docker-Stack (lokal) wurde von diesem Plan nicht angefasst; die Aenderungen liegen im Web-Quellcode und werden erst mit einem `--build`/`--force-recreate` durch den Nutzer wirksam.
## Known Stubs
Keine. Beide neuen Testfaelle in `groups-page.test.tsx` (Namensfallback/Tooltip, D-07-Regression) laufen gegen echte Rendering-Pfade der Komponente, keine hartkodierten Platzhalterwerte.
## Next Phase Readiness
- `GroupFormModal.tsx` ist jetzt der endgueltige, dreizustandsfaehige Dialog fuer Plan 16-05 — kein weiterer Rueckbau an dieser Datei erwartet
- `internalName ?? name` ist jetzt an allen vier geplanten Anzeigestellen implementiert: `listForTenant()` und beide `getUserAccess()`-Projektionen (16-02), sowie die Gruppenliste (dieser Plan). Verbleibend fuer Plan 16-05 laut 16-02-Summary: `admin/modules/grants/page.tsx`-Spaltenkopf (Surface Contract 5)
- Der manuelle Browser-Durchklick aus dem Plan-Verification-Block (drei Dialog-Zustaende inkl. 409-Fehleranzeige, Namensanzeige mit Tooltip nach dem Speichern) ist als WINDOWS.md #5 offen — vor `/gsd-ship` nachzuholen, kein Browser-Tool in dieser Session verfuegbar
- PERM-02 bleibt bewusst auf Pending — schliesst mit Plan 16-05
---
*Phase: 16-ad-gruppen-synchronisation*
*Completed: 2026-08-06*
## Self-Check: PASSED
Alle 5 in diesem Plan geaenderten Quelldateien sowie dieses Summary existieren auf der Festplatte; alle 4 Commit-Hashes (30affbb, 6802b48, e900b43, 6b33bb0) sind im lokalen Git-Log auffindbar.