9.3 KiB
phase, slug, status, shadcn_initialized, preset, created
| phase | slug | status | shadcn_initialized | preset | created |
|---|---|---|---|---|---|
| 9 | cert-manager-module | draft | false | none | 2026-07-01 |
Phase 9 — UI Design Contract
Visual and interaction contract for Phase 9: Cert Manager Module. Generated by gsd-ui-researcher. Verified by gsd-ui-checker.
Design System
| Property | Value |
|---|---|
| Tool | none — custom Tailwind utilities, kein shadcn |
| Preset | not applicable |
| Component library | none (kein Radix, kein shadcn) |
| Icon library | inline SVG (Projektkonvention: keine externe Icon-Lib installiert) |
| Font | Inter (CSS-Variable --font-sans, definiert in globals.css) |
Quelle: apps/web/package.json — kein lucide-react, kein @radix-ui. apps/web/src/components/theme-toggle.tsx bestaetigt inline-SVG-Pattern.
Spacing Scale
Declared values (must be multiples of 4):
| Token | Value | Usage |
|---|---|---|
| xs | 4px | Icon-Gaps, mt-1 Subtexte |
| sm | 8px | Inline-Padding, Button-Gaps |
| md | 16px | space-y-4, Formularfelder-Abstand |
| lg | 24px | p-6, space-y-6, Card-Padding, Tab-Abstand |
| xl | 32px | Sektionsabstand innerhalb einer Card |
| 2xl | 48px | Groeßere Seitenbereich-Trennung |
| 3xl | 64px | nicht verwendet in Modul-Seiten |
Exceptions: Keine — alle vorhandenen Modul-Seiten (domaincheck, dkv-fleet) verwenden ausschliesslich Vielfache von 4px.
Typography
| Role | Size | Weight | Line Height |
|---|---|---|---|
| Body | 16px (text-base) |
400 (regular) | 1.5 |
| Label / Small | 14px (text-sm) |
400 (regular) | 1.5 |
| Heading (Seitentitel) | 24px (text-2xl) |
700 (bold) | 1.2 |
| Subheading (Tab-Sektionen) | 18px (text-lg) |
700 (bold) | 1.3 |
Quelle: Domaincheck-Seite: h1 className="text-2xl font-bold tracking-tight" + text-sm text-muted-foreground. Alle Modul-Seiten folgen diesem Schema.
Color
| Role | Value | Usage |
|---|---|---|
| Dominant (60%) | var(--background) — oklch(0.99 0 0) light / oklch(0.23 0.01 260) dark |
Seitenhintergrund, Tab-Panel-Hintergrund |
| Secondary (30%) | var(--card) — oklch(1 0 0) light / oklch(0.27 0.01 260) dark |
Haupt-Card-Container (bg-card border border-border rounded-lg), Drop-Zone-Hintergrund |
| Accent (10%) | var(--primary) — oklch(0.91 0.19 102) (gelb-gruen, beide Modi) |
Primaer-Aktions-Buttons (Analysieren / Aufteilen / Zusammenfuehren / Konvertieren), aktiver Tab-Indikator, Fokus-Ring |
| Destructive | var(--destructive) — oklch(0.55 0.2 27) |
Fehlermeldungen, Fehler-Zustandstext |
Accent reserved for:
- Primaer-Aktions-Button je Tab (genau ein Button pro Tab im aktiven Zustand)
- Aktiver Tab-Indikator (Unterstrich oder Hintergrund)
- Fokus-Ring (
ring-ring) bei Datei-Upload und Textarea-Inputs
Hinweis: Muted-Foreground (var(--muted-foreground)) fuer beschreibende Subtexte und Platzhalter. Download-Buttons sekundaer (bg-secondary text-secondary-foreground), nicht Accent.
Layout Contract
Seitenbreite: max-w-4xl mx-auto — breiter als domaincheck (max-w-2xl) wegen 4-Tab-Navigation und paralleler Formularelemente (Datei-Liste + Aktionsbereich).
Seiten-Padding: p-6 (24px allseitig).
Struktur (von oben nach unten):
[Seitentitel h1 + Beschreibung p.text-sm.text-muted-foreground]
[Shared Input Card — rounded-lg border border-border bg-card p-6]
├── Datei-Drop-Zone (drag-over: border-primary, border-dashed border-2)
├── ODER-Trennlinie (text-xs text-muted-foreground)
├── PEM-Textarea (auto-detected wenn "-----BEGIN" erkannt)
└── Passwort-Feld (KONDITIONELL — erscheint nur wenn PFX/P12 erkannt oder als Output gewaehlt)
[Tab-Navigation: Analysieren | Aufteilen | Zusammenfuehren | Konvertieren]
(Tab-Bar: border-b border-border, aktiver Tab: border-b-2 border-primary text-foreground,
inaktiver Tab: text-muted-foreground hover:text-foreground)
[Tab-Inhalts-Card — rounded-lg border border-border bg-card p-6]
└── [Ergebnis-Bereich — erscheint nach Verarbeitung, inline unterhalb der Aktion]
Drop-Zone-Verhalten:
- Leer:
border-2 border-dashed border-border rounded-lg p-8mit Text "Datei hierher ziehen oder klicken" - Drag-Over:
border-primary bg-primary/5 - Datei geladen: Dateiname + Groesse angezeigt, X-Button zum Entfernen
Ergebnis-Bereich (pro Tab):
- Analysieren: Key-Value-Grid (2 Spalten,
grid grid-cols-2 gap-2 text-sm) - Aufteilen: Liste einzelner Zertifikate mit je einem Download-Button (
bg-secondary) - Zusammenfuehren: Ein Download-Button fuer die erzeugte Datei
- Konvertieren: Ein Download-Button fuer die konvertierte Datei
Conditional Elements
| Element | Sichtbarkeits-Bedingung |
|---|---|
| Passwort-Feld (Input) | Hochgeladene Datei hat .pfx / .p12 Extension ODER ausgewaehltes Ausgabeformat ist PFX |
| PEM-Textarea | Immer sichtbar als Alternative zur Datei-Drop-Zone |
| ODER-Trennlinie | Immer sichtbar zwischen Drop-Zone und Textarea |
| Ergebnis-Bereich | Nur nach erfolgreichem API-Aufruf |
| Fehler-Bereich | Nur bei API-Fehler (text-sm text-destructive) |
| Lade-Indikator | Waehrend API-Anfrage laeuft (Button disabled + Spinner oder "..." Label-Wechsel) |
Copywriting Contract
| Element | Deutsch | Kontext |
|---|---|---|
| Seitentitel | "Zertifikat-Manager" | h1, t('certManager.title') |
| Seitenbeschreibung | "Zertifikate analysieren, aufteilen, zusammenfuehren und konvertieren." | p.text-sm, t('certManager.description') |
| Tab 1 | "Analysieren" | Tab-Label |
| Tab 2 | "Aufteilen" | Tab-Label |
| Tab 3 | "Zusammenfuehren" | Tab-Label |
| Tab 4 | "Konvertieren" | Tab-Label |
| Drop-Zone leer | "Datei hierher ziehen oder klicken" | Drop-Zone-Platzhalter, t('certManager.dropZone.placeholder') |
| Drop-Zone Formate | ".pem, .crt, .cer, .der, .pfx, .p12, .p7b, .p7c" | Subtext unter Drop-Zone |
| Textarea Platzhalter | "PEM-Inhalt einfuegen (-----BEGIN ...)" | Textarea, t('certManager.paste.placeholder') |
| Passwort-Label | "Passwort (PFX/P12)" | Input-Label |
| Primaer-CTA Tab 1 | "Analysieren" | Button (t('certManager.actions.inspect')) |
| Primaer-CTA Tab 2 | "Aufteilen" | Button (t('certManager.actions.split')) |
| Primaer-CTA Tab 3 | "Zusammenfuehren" | Button (t('certManager.actions.merge')) |
| Primaer-CTA Tab 4 | "Konvertieren" | Button (t('certManager.actions.convert')) |
| Download-Button | "Herunterladen" | Je Ergebnis-Eintrag (t('certManager.actions.download')) |
| Leer-Zustand Analysieren | "Kein Zertifikat geladen." + "Lade eine Datei hoch oder fuege PEM-Text ein." | Empty state |
| Leer-Zustand Aufteilen | "Keine Datei geladen." + "Lade eine Fullchain- oder P7B-Datei hoch." | Empty state |
| Leer-Zustand Zusammenfuehren | "Keine Zertifikate ausgewaehlt." + "Lade mindestens zwei Dateien hoch." | Empty state |
| Leer-Zustand Konvertieren | "Keine Datei geladen." + "Lade eine Datei hoch und waehle ein Ausgabeformat." | Empty state |
| Fehler-Zustand (allgemein) | "Verarbeitung fehlgeschlagen. Pruefe das Dateiformat oder das Passwort." | text-sm text-destructive |
| Fehler: falsches Passwort | "Falsches Passwort. PFX/P12-Datei konnte nicht entschluesselt werden." | Spezifischer node-forge Fehler |
| Fehler: ungueltiges Format | "Unbekanntes Format. Die Datei konnte nicht als Zertifikat erkannt werden." | node-forge Parsing-Fehler |
| ODER-Trennlinie | "oder" | Zwischen Drop-Zone und Textarea |
Interaction Contract
Datei-Upload:
accept=".pem,.crt,.cer,.der,.pfx,.p12,.p7b,.p7c"als client-seitiger Guard- Drag-and-Drop auf Drop-Zone und Click-to-Browse beide unterstuetzt
- Nach Auswahl: Dateiname + Dateityp sofort angezeigt, Textarea wird geleert
- Nach Paste in Textarea: Drop-Zone zeigt "PEM-Text eingefuegt" an, Datei-Auswahl wird geleert
- Nur jeweils eine Quelle gleichzeitig aktiv (Datei ODER Paste)
Passwort-Feld:
- Typ:
input[type="password"]mit Toggle (Auge-Icon als inline SVG) - Erscheint via CSS-Klasse / conditional render — kein Reflow der umgebenden Card
- Bei Aufteilen/Analysieren: Passwort-Feld fuer Input-Entschluesselung
- Bei Zusammenfuehren (PFX-Output): Passwort-Feld fuer Output-Verschluesselung
Tab-Wechsel:
- Tab-Wechsel leert NICHT die Drop-Zone / Textarea — Eingabe bleibt erhalten
- Ergebnis-Bereich wird bei Tab-Wechsel geleert (neuer Tab = neue Aktion)
- Aktiver Tab behält den State bis explizit eine neue Datei geladen wird
Lade-Zustand:
- Button disabled + Label aendert sich zu "Wird verarbeitet..." (
t('certManager.actions.processing')) - Kein Spinner-Element — Label-Wechsel genuegt fuer kurze API-Calls
Download:
<a href="..." download="dateiname.ext">mit vom Backend gesetztem Dateinamen- Kein gesondertes Modal — direkter Browser-Download-Dialog
Registry Safety
| Registry | Blocks Used | Safety Gate |
|---|---|---|
| shadcn official | none | not applicable — shadcn nicht initialisiert |
| third-party | none | not applicable |
Keine dritten Registrierungen. Alle Komponenten sind eigene Tailwind-Utility-Klassen. node-forge ist eine Backend-Dependency (NestJS API), kein Frontend-Bundle.
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