Files
2026-07-01 15:49:15 +02:00

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:

  1. Primaer-Aktions-Button je Tab (genau ein Button pro Tab im aktiven Zustand)
  2. Aktiver Tab-Indikator (Unterstrich oder Hintergrund)
  3. 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-8 mit 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