From 09d52311481e450dffd9feafb5d3641fa24473ff Mon Sep 17 00:00:00 2001 From: Schalli Date: Wed, 1 Jul 2026 15:47:00 +0200 Subject: [PATCH] docs(09): UI design contract --- .../09-cert-manager-module/09-UI-SPEC.md | 211 ++++++++++++++++++ 1 file changed, 211 insertions(+) create mode 100644 .planning/phases/09-cert-manager-module/09-UI-SPEC.md diff --git a/.planning/phases/09-cert-manager-module/09-UI-SPEC.md b/.planning/phases/09-cert-manager-module/09-UI-SPEC.md new file mode 100644 index 0000000..fd5e6f9 --- /dev/null +++ b/.planning/phases/09-cert-manager-module/09-UI-SPEC.md @@ -0,0 +1,211 @@ +--- +phase: 9 +slug: cert-manager-module +status: draft +shadcn_initialized: false +preset: none +created: 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`) | 600 (semibold) | 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:** +- `` 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