23 KiB
phase, slug, status, shadcn_initialized, preset, created
| phase | slug | status | shadcn_initialized | preset | created |
|---|---|---|---|---|---|
| 15 | modul-berechtigungen-gruppen-user-grants | draft | false | none | 2026-08-04 |
Phase 15 — UI Design Contract
Visual and interaction contract for frontend phases. Generated by gsd-ui-researcher, verified by gsd-ui-checker.
Design System
| Property | Value |
|---|---|
| Tool | none — kein components.json, keine shadcn-CLI im Repo (verifiziert: find liefert keinen Treffer) |
| Preset | not applicable |
| Component library | none — keine Radix-/Base-UI-Abhängigkeit im Projekt (grep radix|lucide|cva|clsx in apps/web/package.json liefert nichts). Alle fünf bestehenden Admin-Seiten (ldap, users, modules, tenants, smtp) sind handgebaute Tailwind-Komponenten mit einem konsistenten, projekteigenen Vokabular (Tabellen, Modal-Dialoge, Toggle-Switches, Badge-Chips) |
| Icon library | none — Icons sind handgeschriebene Inline-<svg> im Lucide-Stil (viewBox="0 0 24 24", stroke="currentColor", strokeWidth="2", strokeLinecap="round", strokeLinejoin="round", kein fill), siehe apps/web/src/components/admin/admin-sidebar.tsx |
| Font | Inter via --font-sans (apps/web/src/app/globals.css:33), system-ui-Fallback |
Shadcn-Gate-Entscheidung: Kein components.json gefunden, Stack ist Next.js — der Gate würde normalerweise fragen, ob shadcn initialisiert werden soll. Diese Frage ist hier bereits beantwortet: der <hard_constraints>-Auftrag verlangt explizit "Reuse existing components and tokens; do not introduce a new component library or a second styling approach", und 14 vorherige Phasen haben ein durchgängiges, funktionierendes handgebautes System etabliert (5 Admin-Seiten, Marketplace, Modul-Shells). shadcn jetzt einzuführen würde einen zweiten, inkonsistenten Styling-Ansatz neben den bestehenden ~40 Seiten schaffen. Tool bleibt none, Registry-Safety-Gate ist damit nicht anwendbar.
Spacing Scale
Declared values (must be multiples of 4), identisch mit dem bereits im Projekt etablierten Raster (verifiziert in allen fünf Admin-Seiten):
| Token | Value | Usage |
|---|---|---|
| xs | 4px | Icon-Text-Abstand in Badges/Chips (gap-1), Checkbox-zu-Label-Feinabstand |
| sm | 8px | Kompakte Element-Abstände (gap-2), Badge-Innenpolsterung (px-2 py-0.5) |
| md | 16px | Standard-Formularabstand (space-y-4), Tabellenzellen-Polsterung (px-4 py-3) |
| lg | 24px | Card-/Section-Innenpolsterung (p-6), Abstand zwischen Seitenabschnitten (space-y-6/space-y-8) |
| xl | 32px | reserviert, in dieser Phase nicht benötigt |
| 2xl | 48px | reserviert, in dieser Phase nicht benötigt |
| 3xl | 64px | reserviert, in dieser Phase nicht benötigt |
Exceptions:
- Icon-only Aktionsbuttons in Tabellenzeilen (Standardgruppen-Stern-Toggle, Mitglied-Entfernen-Button) übernehmen das bestehende Projektmaß
px-2 py-1(kein 44px-Touch-Target) — Admin-Oberflächen sind Desktop-Maus-Bedienung, identisch zu den bereits ausgelieferten "Bearbeiten"/"Löschen"-Buttons inadmin/users/page.tsx. Kein neuer Präzedenzfall, reine Fortführung. - Matrix-Checkboxen (Permission-Matrix-Seite) nutzen die native
<input type="checkbox">-Größe ohne zusätzliches Padding, identisch zum bestehendengroupFilterDns-Checkbox-Muster inadmin/ldap/page.tsx.
Typography
Übernommene, bereits im Projekt etablierte Werte (nicht neu erfunden — verifiziert aus allen fünf Admin-Seiten). Vier Größen und vier Gewichte sind bereits Teil des bestehenden Systems; diese Phase führt keine neue Größe und kein neues Gewicht ein, sondern verwendet ausschließlich die vorhandenen vier Kombinationen:
| Role | Size | Weight | Line Height |
|---|---|---|---|
| Label / Badge | 12px (text-xs) |
500 medium | 1.4 |
| Body / Table / Form | 14px (text-sm) |
400 regular (Fließtext, Tabellenzellen) | 1.5 |
| Section Heading | 18px (text-lg) |
600 semibold | 1.3 |
| Page Heading | 24px (text-2xl) |
700 bold | 1.2 |
Exception (dokumentierte Abweichung vom UI-SPEC-Standardvokabular "2 Gewichte max"): Formular-Labels, Tabellen-Kopfzeilen und Buttons nutzen zusätzlich font-medium (500) auf 14px-Basis — z. B. <label className="text-sm font-medium text-foreground">. Das ist kein fünftes Gewicht, sondern dieselbe 500er-Stufe wie bei Labels/Badges, nur auf 14px statt 12px angewendet. Diese vier Größe-Gewicht-Paare sind das etablierte Tessera-Admin-Vokabular seit Phase 2 — der Planner/Executor übernimmt sie unverändert, erfindet keine neue Kombination.
Color
| Role | Value | Usage |
|---|---|---|
| Dominant (60%) | --background / --card (OKLCH, hell 0.99 0 0 / 1 0 0, dunkel 0.23 0.01 260 / 0.27 0.01 260) |
Seitenhintergrund, Card-/Section-Flächen |
| Secondary (30%) | --secondary / --muted / --sidebar (OKLCH 0.96 0 0 hell, 0.30 0.01 260 dunkel) |
Tabellenkopf (bg-muted/50), Admin-Sidebar, sekundäre Buttons (border border-border) |
| Accent (10%) | --primary (OKLCH 0.91 0.19 102, gelb) |
ausschließlich: primäre CTA-Buttons ("Gruppe erstellen", "Sofort freigeben", "Speichern"), aktive Toggle-Switch-Füllung (Modul-Aktivierung, Matrix-Häkchen-Fokusring), aktiver Zustand im Admin-Sidebar-Nav-Item (sidebar-accent-foreground), Fokus-Ring (ring-ring) |
| Destructive | --destructive (OKLCH 0.55 0.2 27, rot) |
ausschließlich: "Löschen"/"Entfernen"/"Freigabe entziehen"-Buttons, Bestätigungs-Button in Lösch-Dialogen, Fehlermeldungstext |
Accent reserved for: Primär-Buttons (Gruppe erstellen, Speichern, Sofort freigeben), aktiver Toggle-Zustand, aktives Sidebar-Nav-Item, Fokus-Ring. Niemals für Badges, Tabellenzeilen-Hover oder informative Hinweise — dafür gilt die bestehende informelle Badge-Palette unten.
Bestehende informelle Badge-Palette (kein Token, aber projektweit etabliertes Muster — jede Instanz trägt Light- und Dark-Variante):
| Badge-Zweck | Klassen | Bereits verwendet in |
|---|---|---|
| Positiv/Aktiv | bg-green-100 text-green-700 dark:bg-green-900/30 dark:text-green-400 |
Modul-Status "Aktiviert", User-Status "Aktiv" |
| Neutral/Inaktiv | bg-gray-100 text-gray-500 dark:bg-gray-800 dark:text-gray-500 bzw. bg-muted text-muted-foreground |
User-Status "Inaktiv", Marketplace "Verfügbar" |
| Info/System | bg-blue-100 text-blue-700 dark:bg-blue-900/30 dark:text-blue-400 |
Rolle ADMIN, "System"-Modul-Badge |
| Kritisch (Rolle) | bg-red-100 text-red-700 dark:bg-red-900/30 dark:text-red-400 |
Rolle SUPER_ADMIN |
| NEU in dieser Phase — Eingeschränkt | bg-amber-100 text-amber-700 dark:bg-amber-900/30 dark:text-amber-400 |
Marketplace-Badge "Kein Zugriff" (D-08). Bewusst nicht destructive (rot) — es ist kein Fehlerzustand, sondern ein Informationszustand, konsistent mit der bestehenden Palette-Familie um eine fünfte, bisher ungenutzte Farbe erweitert (amber ist in Tailwinds Standardpalette bereits verfügbar, kein neues Token nötig) |
Copywriting Contract
| Element | Copy |
|---|---|
| Primary CTA — Gruppenverwaltung | "Gruppe erstellen" (admin.groups.create) |
| Primary CTA — Matrix-Zugriff | "Freigaben-Matrix" (Link/Button auf /admin/modules, adminModules.grantsLink) |
| Primary CTA — Modul-Aktivierung mit Grant | "Sofort freigeben" (adminModules.activationDialog.grantNow) |
| Sekundär-CTA — Modul-Aktivierung ohne Grant | "Später konfigurieren" (adminModules.activationDialog.configureLater) |
| Empty state heading — Gruppenliste | "Keine Gruppen vorhanden" (admin.groups.noGroups) |
| Empty state body — Gruppenliste | "Lege deine erste Gruppe an, um Modulzugriff gezielt zu vergeben." (admin.groups.noGroupsBody) |
| Empty state — Matrix ohne aktive Module | "Es sind noch keine Module für diesen Mandanten aktiviert. Aktiviere zuerst ein Modul unter Module." (adminModules.grants.emptyModules, mit Link zu /admin/modules) |
| Empty state — User-Detail ohne Gruppenmitgliedschaft | "Dieser Benutzer ist keiner Gruppe zugeordnet." (admin.users.grants.noGroups) |
| Error state — Grant-Speicherfehler | "Freigabe konnte nicht gespeichert werden. Bitte erneut versuchen." + Statuscode-Anzeige wie im bestehenden adminModules-Fehlermuster (error-Div mit border-destructive/50 bg-destructive/10) |
| 403-Seite Titel (D-07) | "Kein Zugriff auf dieses Modul" (modules.accessDenied.title) |
| 403-Seite Body (D-07) | "Du hast für dieses Modul keine Freigabe. Wende dich an deinen Administrator." (modules.accessDenied.body) — Formulierung ist durch D-07 wörtlich vorgegeben, keine Umformulierung |
| 403-Seite Rücklink | "Zur Startseite" → / (Dashboard), nicht "Zurück zur Kategorie" — der Benutzer hat für dieses Modul keinen Zugriff, ein Rücksprung in die Modul-Kategorie ist kein sinnvoller nächster Schritt (modules.accessDenied.backToDashboard) |
| Marketplace-Badge "Kein Zugriff" (D-08) | "Kein Zugriff" (marketplace.statusNoAccess) |
| Marketplace-Klick-Feedback bei gesperrter Karte | Toast: "Kein Zugriff auf dieses Modul — wende dich an deinen Administrator." (identischer Wortlaut wie 403-Seite, marketplace.toastNoAccess) — nutzt die bestehende Toast.tsx-Komponente |
| Destructive confirmation — Gruppe löschen (D-17, PERM-01) | Titel: "Gruppe löschen"; Body: "Diese Gruppe hat {memberCount} Mitglieder und {grantCount} Modul-Freigaben. Betroffene Benutzer verlieren den Zugriff, sofern sie ihn nicht anderweitig haben. Diese Aktion kann nicht rückgängig gemacht werden." (admin.groups.deleteConfirm.title / .body, Zahlen als next-intl-Interpolationsparameter, keine generische Warnung ohne Zahlen) |
| Destructive confirmation — Freigabe entziehen (Matrix/User-Detail) | Kein Dialog nötig — Häkchen-Toggle ist sofort wirksam und sofort umkehrbar (kein Datenverlust, reine Klick-Umkehrung), analog zum bestehenden Modul-Aktivierungs-Toggle-Verhalten. Kein Bestätigungsdialog für einzelne Grant-Toggles. |
| Destructive confirmation — Mitglied aus Gruppe entfernen | Kein Dialog — sofortiger Klick-Entfernen-Button analog zum bestehenden userExcludeList-Chip-Entfernen-Muster (kein irreversibler Datenverlust, Mitglied kann sofort wieder hinzugefügt werden) |
| AD-Bindung — Bindungs-Auswahl-Hinweis | "Wähle eine AD-Gruppe aus der Liste, um die Mitgliedschaft automatisch zu synchronisieren." (admin.groups.ldapBind.hint) |
| AD-Bindung — bereits gebunden | "Gebunden an: {ldapDn}" mit "Bindung entfernen"-Link (admin.groups.ldapBind.bound / .unbind) |
| Standardgruppen-Markierung | Icon-Button-Tooltip/aria-label: "Als Standardgruppe festlegen" bzw. "Standardgruppe" (wenn bereits gesetzt) — admin.groups.setDefault / admin.groups.isDefault |
Surface Contracts
1. /admin/groups — Gruppenverwaltung (PERM-01, D-14, D-17)
Sechster Eintrag im AdminSidebar (apps/web/src/components/admin/admin-sidebar.tsx), zwischen Module und LDAP oder ans Ende — Reihenfolge liegt bei Executor, Icon muss sich vom bestehenden admin.users-Personen-Icon unterscheiden (Vorschlag: ein Roster-/Listen-Icon, 24×24, stroke-width 2, kein Fill — kein bestehendes Icon im Projekt duplizieren).
Seitenaufbau identisch zu admin/users/page.tsx:
h1"Gruppen" (24px/700) + primärer Button "Gruppe erstellen" rechtsbündig im Header (identisches Markup wieopenCreate-Button inAdminUsersPage).- Tabelle (
rounded-md border border-border,thead bg-muted/50,tbody divide-y divide-border) mit Spalten: Name, AD-Bindung (Badge "AD-gebunden" info-blau wennldapDngesetzt, sonst Badge "Manuell" neutral-grau), Standardgruppe (Stern-Icon-Toggle-Button, gefüllt wennisDefault), Mitglieder (Zahl,{count}), Aktionen (Bearbeiten · Mitglieder · Löschen als drei Text-Buttons im bestehendenpx-2 py-1 text-xs-Stil). - Create/Rename-Modal: identisches Overlay-Muster (
fixed inset-0 bg-black/50→max-w-md rounded-lg border bg-card p-6 shadow-lg) wie das User-Create-Modal. Feld "Name" (Pflicht), darunter Abschnitt "AD-Bindung (optional)": Suchfeld + Ergebnisliste im 1:1-Muster der bestehendengroupFilter.discover-Sektion ausadmin/ldap/page.tsx(Zeilen 705–746), aber mit Radio-Auswahl statt Checkbox-Mehrfachauswahl (genau eine AD-Gruppe pro Tessera-Gruppe, D-05). - Mitglieder-Modal (separater Dialog, Button "Mitglieder" aus der Tabellenzeile): zwei Abschnitte — (a) aktuelle Mitglieder als Chip-Liste mit Herkunfts-Badge (
MANUAL/LDAP) und Entfernen-Button, identisches Chip-Markup wieuserExcludeListinadmin/ldap/page.tsx(Zeilen 957–974); LDAP-Mitgliedschaften zeigen keinen Entfernen-Button (D-19: nur der Sync entfernt sie) — stattdessen ein deaktivierter Zustand mit Tooltip "Wird über AD-Sync verwaltet". (b) Suchfeld + Checkbox-Liste bestehender Tenant-Benutzer zum manuellen Hinzufügen, identisches Markup wie dieuserSearch-Sektion (Zeilen 841–877), Ziel ist hier aberGroupMembership, nicht LDAP-Import. - Lösch-Dialog: identisches Overlay-Muster wie
deleteConfirminAdminUsersPage, aber mit den unter Copywriting Contract festgelegten konkreten Zahlen (D-17) statt der generischen Ein-Satz-Warnung.
2. Permission-Matrix — Module × Gruppen (PERM-03, D-15)
Erreichbar über einen Button "Freigaben-Matrix" im Header von /admin/modules (kein siebter Sidebar-Eintrag — bleibt Unterseite von Modules, analog zu dkv-fleet/settings). Route: /admin/modules/grants.
- Suchfeld oben (filtert Modul- und Gruppennamen client-seitig), identisches Eingabefeld-Markup wie
discoverSearch. - Tabelle mit
stickyerster Spalte (Modulname,sticky left-0 bg-card z-10) undstickyKopfzeile (Gruppennamen,sticky top-0 bg-card z-10) in einemoverflow-x-auto overflow-y-auto max-h-[70vh] rounded-md border border-border-Container — löst die Claude's-Discretion-Frage "Scrollverhalten bei vielen Modulen/Gruppen" aus RESEARCH.md. - Zeilen nach Modul-Kategorie gruppiert: volle-Breite-Zwischenzeile
bg-muted/50mit Kategorienamen (identisch zum bestehenden Kategorie-Badge-Text ausAdminModulesPage), darunter die Modulzeilen dieser Kategorie. - Jede Zelle: natives
<input type="checkbox">, sofortiges Toggle bei Klick (optimistisches UI, PATCH im Hintergrund — identisches Verhalten zum bestehenden Modul-Aktivierungs-Switch inAdminModulesPage.toggleModule), kleiner Inline-Spinner nur in der aktiven Zelle während des Requests (kein Vollseiten-Ladezustand). - Fußnote unter der Tabelle (12px,
text-muted-foreground): "ADMIN und SUPER_ADMIN haben immer Zugriff auf alle aktiven Module — diese Matrix betrifft nur die Rolle USER." (D-03 transparent machen, verhindert Verwirrung, warum Admin-Accounts hier nicht auftauchen). - Leerer Zustand (kein aktives Modul im Mandanten): siehe Copywriting Contract "Empty state — Matrix ohne aktive Module".
3. User-Detail-Grants in /admin/users (PERM-03, D-16)
AdminUsersPage bekommt pro Zeile einen vierten Aktions-Button "Details" (neben Bearbeiten/Löschen, gleicher px-2 py-1 text-xs-Stil), der ein breiteres Modal öffnet (max-w-2xl statt max-w-md — mehr Inhalt als das einfache Edit-Formular, aber weiterhin das etablierte Overlay-Muster, kein neues Slide-over-Primitiv).
Modal-Inhalt, Titel "{username} — Zugriff":
- Abschnitt "Gruppenmitgliedschaften" — read-only Chip-Liste (identisches Chip-Markup wie
userExcludeList, aber ohne Entfernen-Button — Bearbeitung geschieht ausschließlich unter/admin/groups, keine zweite CRUD-Oberfläche für dieselbe Beziehung). Leerzustand siehe Copywriting Contract. - Abschnitt "Modul-Zugriff" — Tabelle mit Spalten: Modul | Über Gruppe(n) (Chip-Liste der Gruppennamen, die dieses Modul gewähren, oder "–") | Direkt (Checkbox-Toggle für einen direkten
ModuleGrant, identisches Sofort-Toggle-Verhalten wie die Matrix). Nur mandantenweit aktive Module werden gelistet — inaktive Module sind hier nicht wählbar, da ein Grant ohne Aktivierung wirkungslos ist (D-02).
4. 403-Seite für gesperrtes Modul (PERM-04, D-07)
apps/web/src/app/(portal)/modules/[category]/[moduleSlug]/page.tsx wird zur async Server Component (Umbau lt. RESEARCH.md Pattern 5); der bisherige Client-Inhalt wandert unverändert in eine neue module-shell.tsx-Client-Component.
Visuelles Muster identisch zum bestehenden "not found"-Zustand derselben Datei (Zeilen 30–65: zentrierter Block, rounded-lg bg-muted p-4-Icon-Container, h2 18px/600, p 14px muted, Link darunter) — nur Icon und Copy ändern sich:
- Icon: Schloss-Symbol (
lock, nicht das bestehende Kreis-mit-Schrägstrich-"nicht gefunden"-Icon — visuelle Unterscheidung zwischen "existiert nicht" und "gesperrt" ist bewusst, verhindert Verwechslung der beiden Zustände). - Titel/Body: siehe Copywriting Contract, wörtlich D-07.
- Kein
notFound(), kein Redirect (D-07 explizit) — die Seite rendert das 403-Markup direkt als Server-Response. - Rücklink führt zur Startseite (
/), nicht zur Modul-Kategorie (siehe Copywriting Contract Begründung).
5. Marketplace-Katalog — "Kein Zugriff"-Badge (PERM-04, D-08)
MarketplaceCard.tsx bekommt einen dritten Status neben isActive/!isActive: hasGrant (nur relevant, wenn isActive === true und Rolle USER). Wenn isActive && !hasGrant:
- Zusätzliches Badge "Kein Zugriff" (amber, siehe Color-Tabelle) direkt neben dem bestehenden Status-Badge in derselben Badge-Reihe (
flex items-center gap-2). - Karte bekommt
opacity-60 cursor-not-allowedauf dem äußerendiv(keinhover:shadow-md/hover:border-primary/30in diesem Zustand — visuelles Feedback, dass die Karte nicht interaktiv ist). - Klick auf die Karte navigiert nicht zum Moduldetail, sondern löst den Toast "Kein Zugriff …" aus (bestehende
Toast.tsx-Komponente aus dem Marketplace-Ordner wiederverwenden, kein neues Toast-System). - ADMIN/SUPER_ADMIN sehen dieses Badge nie (Bypass, D-03) — für sie ist jede aktivierte Karte normal klickbar.
6. Modul-Aktivierungs-Dialog in /admin/modules (PERM-03, D-10)
AdminModulesPage.toggleModule verzweigt: Deaktivierung bleibt unverändert (bestehendes ActivationDialog.tsx-Warnmuster, falls vorhanden/nachgezogen). Aktivierung öffnet neu ein Dialog im identischen Overlay-Stil wie ActivationDialog.tsx (fixed inset-0 bg-black/50 → max-w-sm rounded-lg border bg-card p-6 shadow-lg), aber mit drei Aktionen statt zwei:
- Titel: "Modul aktivieren" + Modulname interpoliert.
- Body: Frage, ob sofort für die Standardgruppe freigegeben werden soll (siehe D-10-Wortlaut in
15-CONTEXT.md). - Buttons, von links nach rechts: "Abbrechen" (Border-Button, bricht komplett ab, Modul bleibt inaktiv) — "Später konfigurieren" (Border-Button, aktiviert das Modul OHNE Grant, Admin konfiguriert Freigaben separat über Matrix/Gruppen) — "Sofort freigeben" (Primary-Button
bg-primary, aktiviert das Modul UND erzeugt einenModuleGrantfür die als Standard markierte Gruppe des Mandanten). - Falls der Mandant keine als Standard markierte Gruppe besitzt (D-13: Markierung kann abgeschaltet sein): "Sofort freigeben" ist
disabledmit Hinweistext darunter ("Keine Standardgruppe markiert — lege zuerst eine unter Gruppen fest oder konfiguriere die Freigabe manuell.").
7. Sidebar (apps/web/src/components/layout/sidebar.tsx)
Explizit keine visuelle Änderung. Die Komponente fetcht weiterhin GET /modules/active unverändert und rendert exakt dasselbe Markup — der Endpoint liefert nach dieser Phase serverseitig weniger Module für nicht-freigegebene Benutzer, das ist reine Datenänderung, keine UI-Änderung. Kein neuer Sidebar-Zustand ("gesperrtes Modul ausgegraut anzeigen" o. ä.) — nicht freigegebene Module fehlen schlicht in der Liste, identisch zum bestehenden Verhalten für nicht-aktivierte Module.
Dashboard-Widgets (PERM-07, D-22)
Ebenfalls keine neue UI-Komponente: DashboardService.getWidgets() filtert serverseitig, das Frontend rendert nur die gefilterte Liste — identisch zum bestehenden Grid-Rendering-Pfad. Kein Platzhalter/"gesperrtes Widget"-Zustand, das Widget verschwindet ersatzlos (D-22 verlangt kein Hinweis-Kachel).
UI Considerations
Applicable state considerations resolved: 9 covered, 3 backstop, 0 unresolved
| Category | Element(s) | Status | Resolution / Reason |
|---|---|---|---|
| empty | Gruppenliste (/admin/groups) |
✅ covered | Leerer Zustand rendert die dokumentierte "Keine Gruppen vorhanden"-Copy mit CTA, identisches Muster zu AdminUsersPage's noUsers-Zustand |
| empty | Permission-Matrix ohne aktive Module | ✅ covered | Dokumentierte Empty-State-Copy mit Link zu /admin/modules |
| empty | User-Detail ohne Gruppenmitgliedschaft | ✅ covered | Dokumentierte Empty-State-Copy, Chip-Liste einfach leer |
| empty | User-Detail-Modul-Zugriff-Tabelle, wenn Mandant keine aktiven Module hat | 🧪 backstop | Kein explizit dokumentierter Text — Tabelle rendert 0 Zeilen; Verifikation prüft, dass kein leerer <table>-Rahmen ohne jeden Hinweis erscheint |
| loading | Matrix-Zellen-Toggle, Gruppen-Modal-Speichern | ✅ covered | Identisches Inline-Spinner-Muster wie bestehender isToggling-Zustand in AdminModulesPage — kein neues Ladeverhalten |
| error | Grant-Speicherfehler | ✅ covered | Dokumentierte Fehlermeldung im bestehenden error-Div-Muster |
| error | Gruppe löschen schlägt fehl (z. B. Netzwerkfehler) | 🧪 backstop | Kein bestehendes Netzwerkfehler-Muster für Lösch-Dialoge im Projekt (aktuelle Lösch-Dialoge scheitern "silently" laut Code-Kommentar) — Planner muss entscheiden, ob dieser Präzedenzfall (stiller Fail) hier ebenfalls gilt oder eine sichtbare Fehlermeldung nötig ist, da PERM-01 konkrete Zahlen verspricht, die bei Fehlschlag nicht veralten dürfen |
| populated | Matrix mit vielen Modulen/Gruppen | ✅ covered | Sticky Header + sticky erste Spalte + Kategorie-Gruppierung + Suchfeld, siehe Surface Contract 2 |
| overflow | Lange Gruppennamen in der Matrix-Kopfzeile | 🧪 backstop | Kein expliziter Truncate-/Tooltip-Mechanismus dokumentiert — Planner sollte truncate + title-Attribut auf den Spaltenkopf anwenden, analog zum bestehenden truncate-Einsatz in MarketplaceCard.tsx (h3.truncate) |
| zero-one-many | AD-Bindung: Gruppe ohne/mit gebundener AD-Gruppe | ✅ covered | Zwei explizite Zustände dokumentiert (ungebunden zeigt Auswahl-UI, gebunden zeigt Chip + Entfernen-Link) |
| zero-one-many | Standardgruppe: keine/eine markierte Gruppe pro Mandant | ✅ covered | D-13 erzwingt DB-seitig genau 0 oder 1 — UI zeigt Stern-Toggle je Zeile, "Sofort freigeben" wird disabled, wenn keine Standardgruppe existiert (Surface Contract 6) |
| long-text | Modulnamen in der Matrix-ersten-Spalte | ✅ covered | sticky left-0-Spalte erhält feste Mindestbreite + truncate mit title-Attribut, identisch zur Overflow-Behandlung in Zeile darüber |
Registry Safety
| Registry | Blocks Used | Safety Gate |
|---|---|---|
| shadcn official | keine — Tool ist none |
not required |
| Drittanbieter | keine | not required |
Kein Registry-Zugriff in dieser Phase — alle Komponenten sind handgebaut nach den in "Surface Contracts" referenzierten, bereits im Repo vorhandenen Mustern.
Checker Sign-Off
- Dimension 1 Copywriting: PASS
- Dimension 2 Visuals: PASS
- Dimension 3 Color: PASS
- Dimension 4 Typography: PASS
- Dimension 5 Spacing: PASS
- Dimension 6 Registry Safety: PASS
Approval: pending