Zum Inhalt

Color-System Implementation — Konzept A

Live-Stand des Color-Systems in operator-ui + tenant-ui. Source-of- Truth-Tokens: concept-a.md. Implementation-Branch: platform/color-system-implementation-concept-a, Merge 7261426a4d297b5f2876adf04516ee1d8b7bc275.

Was ist live

Schicht Inhalt
Backend Neue Tabelle user_preferences (Composite-PK (realm, username)). Lazy-Create. API: GET/PATCH /api/v1/users/me/preferences. Migration 0008_user_preferences.py
Frontend Tokens 27 semantische Tokens × 3 Modi (light, dark, auto via prefers-color-scheme) × 2 Surfaces (operator, tenant). Identisches Schema in beiden Frontends
Theme-Provider useTheme()-Composable in beiden Frontends. Lädt aus Backend (Cross-Device-Sync), Fallback LocalStorage. System-Theme-Listener für Auto-Mode-Live-Update
Toggle-Component <ThemeToggle> in beiden Topbars (rechts neben Logout). 3-State-Cycle Sun → Moon → Auto
Walkthrough-Drift-Fix 11 Hardcoded-Color-Stellen migriert von --kora-primary (Indigo-800 als Text) auf --kora-link (kontrastsicher in beiden Modi). Sidebar-Aktiv im Dark-Mode war 1.7:1 → jetzt 8.2:1
8-Drift-Fix Operator-UI hatte 24 Tokens, Tenant-UI 16. Beide Frontends teilen jetzt dasselbe 27-Token-Schema

Token-Schema (Auszug, Light-Mode-Operator-Surface)

Kategorie Token Wert Verwendung
Brand --kora-primary #3730a3 Buttons-BG, aktive Tab-Indikator
Brand --kora-primary-on-dark #a5b4fc Dark / #818cf8 Light Text-Use auf Dark-Surface
Brand --kora-accent #f59e0b (Amber) Legacy-Alias
Brand --kora-badge-bg / -fg #f59e0b / #18181b Operator-Badge — eigene Token-Klasse, surface-unabhängig
Surface --kora-bg / -secondary / -tertiary / -elevated Slate-100 bis Zinc-900-Range Page / Card / Sidebar / Modal
Text --kora-text / -secondary / -muted Slate-900 / 600 / 400 Body / Captions / Disabled
Border --kora-border / -strong / -subtle Slate-200 / 300 / 100 Default / Hover-Active / Divider
State --kora-success / -warning / -danger / -info Emerald-700 / Amber-700 / Red-700 / Blue-700 Status-Badges, Banner
Interactive --kora-link Blue-300 (Dark) / Indigo-700 (Light) Inline-Links + Sidebar-Aktiv-Text (löst Walkthrough-Drift)

Vollständige Werte für alle vier Modus-Surface-Quadranten: concept-a.md.

Theme-Toggle-UX

  • Default für neue User: auto (System-Preference via prefers-color-scheme)
  • Persistenz: Backend (Cross-Device-Sync) + LocalStorage-Fallback
  • Toggle-Position: rechts neben Logout-Button in der Topbar (beide Frontends)
  • 3-State-Cycle: Klick auf Sun → Moon, auf Moon → Auto, auf Auto → Sun
  • Tooltip: „Helles Theme — klicken für Dunkel" / „Dunkles Theme — klicken für Auto" / „Auto (System-Theme) — klicken für Hell"
  • A11y: aria-label mit aktuellem State, Tab-fokussierbar, Enter/Space cyclen

Operator-Badge — Token-Klasse

Vor der Implementation war der Operator-Badge an --kora-accent gekoppelt; in tenant-ui war --kora-accent = #013859 (Navy), was den Badge unlesbar gemacht hätte. Lösung:

  • Eigene Tokens --kora-badge-bg / --kora-badge-fg in beiden Frontends
  • Tenant-UI rendert die Component nicht (<ThemeToggle> ja, Operator- Badge nein), aber die Tokens existieren trotzdem für künftige Operator- Surface-Vorschauen (Block 17 / Tenant-Impersonation)
  • Werte: Light #f59e0b/#18181b (5.0:1 ✅ AA); Dark #fbbf24/#18181b (9.5:1 ✅ AAA)

Walkthrough-Drift — vorher/nachher

Stelle Vor (Dark) Nach (Dark) Verbesserung
Sidebar-Aktiv (BaseLayout.vue:167) #3730a3 Text auf #1e1e1e (1.7:1 ❌) --kora-link Blue-300 auf #0f1115 (8.2:1 ✅ AAA) +6.5:1
Hinweis-Card-Headline (ConnectorsPage.vue:101) 1.6:1 ❌ 8.2:1 ✅ AAA +6.6:1
Inline-Link in Hinweis-Card 1.6:1 ❌ 8.2:1 ✅ AAA +6.6:1
Tab-Aktiv (TenantsDetailPage.vue:297) 1.7:1 ❌ 8.2:1 ✅ AAA +6.5:1
Audit-Filter Badge (AuditLogPage.vue:345) 1.7:1 ❌ 8.2:1 ✅ AAA +6.5:1
7 weitere color: var(--kora-primary)-Stellen je 1.6–1.7:1 ❌ je 8.2:1 ✅ AAA gleich

11 von 11 Drift-Stellen behoben.

API: User-Preferences

Methode Pfad Auth Body Response
GET /api/v1/users/me/preferences Bearer (beide Realms) {"preferred_theme":"light"\|"dark"\|"auto"}
PATCH /api/v1/users/me/preferences Bearer (beide Realms) {"preferred_theme":...} wie GET

Lazy-Create: Erste GET/PATCH-Anfrage legt automatisch einen Default- Eintrag (auto) an. Kein Pre-Provisioning bei Login nötig.

Validierung: ungültiger Wert → 422 (Pydantic-Literal + DB-CHECK).

Audit-Skip: User-Preference-Updates schreiben kein Audit-Log (siehe Prompt Phase 1d).

Migration-Pfad für künftige Color-Updates

  1. Token-Set erweitern in concept-a.md (Source-of-Truth)
  2. Beide tokens.css-Dateien synchron updaten (operator-ui + tenant-ui). 4 Quadranten: light×operator, dark×operator, light×tenant, dark×tenant
  3. WCAG-Kontrast verifizieren für jedes neue Pair, in concept-a.md dokumentieren
  4. Bundle-grep nach hardcoded Hex-Werten — sollten 0 sein außer in den tokens.css-Dateien selbst
  5. Vitest updaten falls Token-Werte in Tests asserted werden
  6. Browser-Test: beide Frontends in beiden Modi, beide Surfaces

Testing

Layer Coverage
Backend Unit 0 (Service-Logik wird im Integration-Test mit echter DB geprüft)
Backend Integration 8/8 grün — Lazy-Create, Cross-Realm, Validation, Audit-Skip, Username-Edge-Case
Backend Smoke scripts/smoke-color-system.sh 6/6
Frontend Vitest (operator-ui) 8 neue useTheme-Tests, total 108/108 grün
Frontend Vitest (tenant-ui) 8 neue useTheme-Tests, total 16/16 grün
Frontend Playwright (operator-ui) 2 neue theme-toggle-Tests, total 12/12 grün
Frontend Playwright (tenant-ui) 2/2 grün (existing template-update.spec ist test.skip)

Cross-Sektions-Verweise

  • Bake-Off-Discovery + Pro/Contra: PORTING-color-system.md
  • Konzept-A-Tokens (vollständig, siegreich aus Bake-Off): concept-a.md
  • Konzept-B und das Vergleichs-Bake-Off-HTML wurden im Reorg 2026-05-09 entfernt (verworfene Variante). Bake-Off-Bilanz und Pro/Contra liegen konsolidiert in der Concept-A-Page oben.