From 6b33bb0b2f4dda01632bd1ace750bee8a86fe945 Mon Sep 17 00:00:00 2001 From: Schalli Date: Thu, 6 Aug 2026 16:33:01 +0200 Subject: [PATCH] docs(16-04): add plan summary Documents the GroupFormModal three-state rebuild (D-03/D-04/D-07), the groups-list internalName fallback, and the ldapBind i18n cleanup for Phase 16 Plan 4. --- .../16-04-SUMMARY.md | 196 ++++++++++++++++++ 1 file changed, 196 insertions(+) create mode 100644 .planning/phases/16-ad-gruppen-synchronisation/16-04-SUMMARY.md diff --git a/.planning/phases/16-ad-gruppen-synchronisation/16-04-SUMMARY.md b/.planning/phases/16-ad-gruppen-synchronisation/16-04-SUMMARY.md new file mode 100644 index 0000000..8d02c4e --- /dev/null +++ b/.planning/phases/16-ad-gruppen-synchronisation/16-04-SUMMARY.md @@ -0,0 +1,196 @@ +--- +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*