Files
archivmail/features/PROJ-80-dark-mode-vervollstaendigung.md
T
sysopsandClaude Sonnet 5 0a02bd7112 feat(PROJ-80,PROJ-81): Dark Mode Vervollständigung + Anhang-Online-Vorschau
- PROJ-80: hartkodierte Farben in ModulesTab/TenantsTab/TenantLDAPTab durch
  Theme-Tokens ersetzt, Mail-HTML-Container bewusst hell isoliert, Print
  im Dark Mode auf lesbare (nicht farbidentische) Ausgabe umgestellt
- PROJ-81: neuer AttachmentPreviewDialog für PDF/Bild-Anhänge in /mail/[id],
  MIME-Whitelist ohne SVG, sandboxed iframe ohne allow-same-origin,
  Auto-Retry-Schleife bei Fehlern behoben
- PROJ-82 angelegt: Print-Farbparität als Folge-Ticket (BUG-80-1-Rest)

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WapWkrQusDuBMhaN8WyuXB
2026-08-06 18:00:41 +02:00

17 KiB

PROJ-80: Dark Mode Vervollständigung (Konsistenz + Default/Persistenz)

Status: In Review

Created: 2026-08-06 Last Updated: 2026-08-06

Kontext

Dark-Mode-Grundinfrastruktur existiert bereits (ThemeProvider, ThemeToggle, CSS-dark-Variante in globals.css, eingebunden in navbar.tsx). PROJ-80 schließt Lücken statt Grundfunktion neu zu bauen: (1) fehlende dark:-Klassen/Kontrastprobleme in einzelnen Komponenten/Seiten, (2) Default-Theme-Verhalten und Persistenz.

Dependencies

  • Voraussetzung vorhanden: Theme-Infrastruktur (theme-provider.tsx, theme-toggle.tsx) — kein Blocker, nur Erweiterung

User Stories

  • Als User (jede Rolle: user/admin/auditor) will ich, dass alle Seiten inkl. Admin-Bereich im Dark Mode korrekt lesbar sind, ohne unlesbaren Text oder falsche Hintergründe.
  • Als User will ich, dass die App standardmäßig meine Systemeinstellung (Hell/Dunkel) übernimmt, wenn ich noch keine eigene Wahl getroffen habe.
  • Als User will ich, dass meine Theme-Wahl über Sessions hinweg erhalten bleibt (Browser-Neustart, neues Gerät nicht zwingend erforderlich).
  • Als Admin will ich Tabellen, Dialoge und Formulare im Admin-Bereich (Tenant-Verwaltung, User-Verwaltung, Audit-Log) korrekt im Dark Mode sehen.

Acceptance Criteria

  • Alle Seiten unter src/app/ (inkl. /admin/*, /imap, /search, /mail/[id]) im Dark Mode manuell durchgeklickt, keine unlesbaren Text/Hintergrund-Kombinationen (Kontrast-Check)
  • Alle shadcn/ui-Komponenten und Custom-Komponenten in src/components/ verwenden Theme-Tokens (bg-background, text-foreground etc.) statt hartkodierter Farben (bg-white, text-black, #fff o.ä.)
  • Default-Theme = system (folgt Betriebssystem-Einstellung), wenn User noch nie manuell gewechselt hat
  • Theme-Wahl wird per localStorage persistiert (bereits Standardverhalten von next-themes) — verifizieren, dass es funktioniert
  • Kein Flackern (FOUC) beim Seitenladen (next-themes suppressHydrationWarning korrekt gesetzt in layout.tsx)
  • Diagramme/Charts (falls vorhanden, z.B. Dashboard-Metriken) passen Farben im Dark Mode an
  • Tenant-Logos/Uploads mit hellem Hintergrund erhalten sichtbaren Rahmen/Container im Dark Mode (Lesbarkeit)

Edge Cases

  • Was passiert bei sehr hellen/dunklen Custom-Tenant-Logos (PROJ-61-Kontext)? → Container mit neutralem Hintergrund/Rahmen unabhängig vom Theme
  • Wie verhält sich Drucken/Export (PDF/EML-Ansicht) im Dark Mode? → Druckansicht immer hell erzwingen (print: Tailwind-Variante), keine dunklen Hintergründe im Ausdruck
  • Was passiert wenn localStorage blockiert ist (Privacy-Mode)? → Fällt auf system-Default zurück, kein Crash
  • Wie verhalten sich eingebettete HTML-Mails (Mail-Ansicht, sanitized) im Dark Mode? → Mail-Inhalt bleibt in eigenem isolierten Container mit hellem Hintergrund, damit Original-Formatierung der Mail nicht durch App-Theme verfälscht wird
  • Admin-Bereich mit vielen Tabellen (User-Verwaltung, Audit-Log) — Zebra-Streifen/Hover-States im Dark Mode ausreichend Kontrast?

Technical Requirements (optional)

  • Kein neues Paket nötig (next-themes bereits Dependency)
  • Browser Support: identisch zu bestehendem Frontend-Support (Chrome, Firefox, Safari, Edge)
  • Keine Backend-Änderung nötig (rein Frontend/CSS)

Tech Design (Solution Architect)

A) Komponentenstruktur (was wird angefasst, nichts Neues gebaut)

Root Layout (bereits vorhanden: ThemeProvider + ThemeToggle im Navbar)
+-- Admin-Bereich
|   +-- ModulesTab            <- hartkodierte Farben ersetzen
|   +-- Alle übrigen Admin-Tabs (Tenants, Users, Audit, Retention, ...) <- Stichprobe/Review, laut Scan bereits sauber
+-- Settings-Bereich
|   +-- TotpSection            <- hartkodierte Farben ersetzen
+-- Mail-Ansicht ([id]/page.tsx)
|   +-- HTML-Mail-Container    <- bewusst IMMER hell (isoliert vom App-Theme, siehe Edge Case)
+-- Druck-/Export-Ansicht
|   +-- Print-Stylesheet-Regel <- erzwingt helles Ausgabeschema unabhängig vom Theme
+-- Dashboard/Charts (falls vorhanden in DashboardTab)
    +-- Farbpalette             <- theme-abhängig prüfen/anpassen

Kein neues UI-Element, keine neue Seite. Reine Härtung bestehender Komponenten auf Theme-Tokens.

B) Daten-/Zustandsmodell (Klartext)

Kein neues Datenmodell. Theme-Zustand existiert bereits:

  • Aktuelles Theme (hell/dunkel/system) wird von next-themes verwaltet
  • Gespeichert im Browser (localStorage), nicht in der Datenbank — pro Gerät/Browser, nicht pro User-Account synchronisiert
  • Kein Serverkontakt nötig

C) Tech-Entscheidungen (Begründung)

  • Kein neues State-Management: Bestehende next-themes-Lösung bleibt, da sie bereits Default-Verhalten (System-Präferenz), Persistenz und FOUC-Vermeidung eingebaut mitbringt — nur korrekte Konfiguration in layout.tsx verifizieren statt Neubau.
  • Farben nur über Theme-Tokens (z.B. Hintergrund/Vordergrund-Variablen), nicht fest verdrahtet: Damit ein einziger Wechsel (Hell/Dunkel) automatisch überall greift, statt Komponente für Komponente hart zu pflegen.
  • Mail-Inhalt bewusst vom Theme ausgenommen: E-Mails sind fremder, oft mit fest kodierten Farben gestalteter HTML-Inhalt (Newsletter, Rechnungen). Erzwingt man dort Dark Mode, wird der Inhalt unlesbar/verfälscht. Deshalb eigener isolierter, immer heller Anzeigebereich.
  • Druckausgabe immer hell: Ausdrucke/PDF-Exporte (GoBD-Auditor-Kontext) müssen unabhängig vom Bildschirm-Theme des jeweiligen Nutzers immer lesbar und einheitlich sein.
  • Kein Server-seitiges Speichern der Theme-Wahl: Aufwand/Nutzen — Theme ist reine Anzeigepräferenz, kein Compliance- oder Sync-relevantes Datum. Pro Browser reicht.

D) Abhängigkeiten (Pakete)

Keine neuen Pakete. next-themes ist bereits installiert und im Einsatz.

Umfang laut Code-Scan (Stand 2026-08-06)

Grep auf hartkodierte Farben (bg-white, text-black, bg-gray-*, text-gray-*, #fff, #000) über src/app und src/components: nur 2 Treffer — ModulesTab.tsx und TotpSection.tsx. Rest der Codebasis nutzt bereits Theme-Tokens. Umfang der Nacharbeit ist somit klein; Hauptaufwand liegt im manuellen visuellen Review (Acceptance Criteria), nicht im Massenumbau.

Implementation Notes (Frontend, 2026-08-06)

Umgesetzt:

  • src/components/admin/ModulesTab.tsx: hartkodierte Statusfarben ersetzt. Planned nutzt jetzt Theme-Tokens (bg-muted/text-muted-foreground), die farbigen Status (In Progress/In Review/Deployed/Removed) haben dark:-Varianten mit ausreichendem Kontrast. Die Summary-Bar referenziert jetzt dieselbe statusColors-Map statt eigener Duplikate (vorher drifteten beide auseinander).
  • src/app/mail/[id]/page.tsx: HTML-Mail-Container (iframe-Wrapper) explizit auf bg-white gesetzt — Mail-Inhalt bleibt bewusst vom App-Theme isoliert (Edge Case aus Spec).
  • src/app/globals.css: @media print-Block ergänzt, der bei aktivem Dark Mode (html.dark) helle Ausgabe erzwingt (color-scheme: light, weißer Grund, schwarzer Text, keine Box-Shadows). Screen-Darstellung bleibt unverändert.
  • src/components/admin/tabs/TenantsTab.tsx + TenantLDAPTab.tsx: Logo-Vorschau-Container von bg-muted/30 auf neutral hellen Hintergrund umgestellt, damit dunkle Tenant-Logos in beiden Themes sichtbar bleiben.

Verifiziert, keine Änderung nötig:

  • theme-provider.tsx hatte bereits defaultTheme="system", enableSystem, disableTransitionOnChange; layout.tsx bereits suppressHydrationWarning am <html> — Default-Verhalten und FOUC-Vermeidung erfüllt ohne Eingriff.
  • Persistenz läuft über next-themes-eigenen localStorage-Key; Zugriff ist dort in try/catch gekapselt, blockiertes localStorage fällt auf system zurück.
  • Kein Chart-Code mit hartkodierten Farbwerten gefunden (Grep auf recharts/fill="#"/stroke="#" in src/app + src/components/admin leer) — Chart-Kriterium damit gegenstandslos.
  • Badges mit bg-blue-600 text-white / bg-green-600 text-white (pop3/page.tsx, imap/ImapAccountCard.tsx) bewusst belassen: gesättigte Volltonfarbe mit weißer Schrift ist in beiden Themes kontraststark.
  • settings/TotpSection.tsx behält bg-white für den QR-Code-Container — ein QR braucht hellen Grund, damit Scanner ihn lesen; das ist funktional, nicht Theme-abhängig.

Build: npx tsc --noEmit und npm run build fehlerfrei.

Offen für QA: das manuelle visuelle Durchklicken aller Seiten im Dark Mode (erstes Acceptance-Kriterium) wurde NICHT durchgeführt — das braucht einen laufenden Browser gegen eine Instanz mit Daten.

QA Test Results

Getestet: 2026-08-06 | Methode: Statische Code-Verifikation (Grep-Audit + Konfig-Review)

Testbarkeits-Einschränkung (wichtig)

Der Code ist uncommitted und nicht deployed (git status: M src/app/globals.css, M src/components/admin/ModulesTab.tsx, M .../TenantsTab.tsx, M .../TenantLDAPTab.tsx, M src/app/mail/[id]/page.tsx). Auf 132 läuft der alte Stand. AC1 (manuelles Durchklicken aller Seiten im Dark Mode) und die Browser-/Responsive-Matrix konnten daher nicht verifiziert werden — die Implementation Notes hatten das bereits als offen markiert, das bleibt offen.

Acceptance Criteria

# Kriterium Ergebnis Beleg
1 Alle Seiten im Dark Mode durchgeklickt, Kontrast geprüft NICHT VERIFIZIERBAR Code nicht deployed; erfordert Browser gegen Instanz mit Daten
2 Komponenten nutzen Theme-Tokens statt hartkodierter Farben PASS Grep über src/app+src/components: alle Restfunde begründet, s.u.
3 Default-Theme = system PASS theme-provider.tsx: defaultTheme="system" + enableSystem
4 Persistenz via localStorage PASS (Code) next-themes-Standardverhalten, attribute="class" gesetzt; Laufzeit-Verifikation offen (Teil von AC1)
5 Kein FOUC PASS layout.tsx:16 <html lang="de" suppressHydrationWarning> + disableTransitionOnChange
6 Diagramme/Charts passen Farben an PASS (gegenstandslos) Kein Chart-Framework im Einsatz. Einzige grafische Elemente: Auslastungsbalken in DashboardTab.tsx:373/375 und 452/454 — Track bg-secondary, Füllung bg-primary/bg-destructive/bg-yellow-500, alle themetauglich
7 Tenant-Logos erhalten sichtbaren Container PASS TenantsTab.tsx:328 und TenantLDAPTab.tsx:381 je rounded border p-3/p-4 bg-white. Gegengeprüft: das sind die einzigen Render-Stellen für Tenant-Logos (Grep auf logo_url//api/tenant/logo-Konsumenten) — keine Stelle vergessen

Summe: 6 PASS, 1 nicht verifizierbar (0 FAIL)

Grep-Audit zu AC2 — verbliebene hartkodierte Farben, alle begründet und akzeptiert:

  • ui/dialog.tsx:24, ui/sheet.tsx:24, ui/alert-dialog.tsx:21bg-black/80 Overlays, shadcn-Standard, themeunabhängig korrekt
  • settings/TotpSection.tsx:88 — QR-Code braucht funktional hellen Grund
  • TenantsTab.tsx:328, TenantLDAPTab.tsx:381 — Logo-Container, genau AC7
  • mail/[id]/page.tsx:308 — HTML-Mail-Container, bewusst themeisoliert (Edge Case aus Spec)
  • mail/AttachmentPreviewDialog.tsx:222/246 — Vorschaufläche (PROJ-81), konsistent mit derselben Begründung
  • pop3/page.tsx:224, imap/ImapAccountCard.tsx:19/29/33 — gesättigte Volltonbadges mit weißer Schrift, in beiden Themes kontraststark

Edge Cases

Edge Case Ergebnis
Sehr helle/dunkle Tenant-Logos (PROJ-61-Kontext) PASS — neutral heller Container mit Rahmen
Drucken/Export im Dark Mode TEILWEISE — hell erzwungen, aber Farbinformation geht verloren → BUG-80-1
localStorage blockiert (Privacy-Mode) PASS (Code) — next-themes kapselt den Zugriff, Fallback auf system; Laufzeittest offen
Eingebettete HTML-Mails im Dark Mode PASS — mail/[id]/page.tsx:308 bg-white am iframe-Wrapper, Mail-Dokument hat ohnehin eigenen Kontext
Admin-Tabellen: Zebra/Hover-Kontrast NICHT VERIFIZIERBAR — Teil von AC1, braucht Browser

Bugs

BUG-80-1 — Druckausgabe verliert Farbinformation, je nach Theme unterschiedlich — Severity: Low — Priorität 1

src/app/globals.css, neuer @media print-Block:

html.dark * {
  background-color: transparent !important;
  color: #000 !important;
  ...
}

Der *-Selektor trifft auch bewusst gesetzte Signalfarben: Status-Badges in ModulesTab (In Progress / In Review / Deployed / Removed) und die Auslastungsbalken im Dashboard werden im Ausdruck vollständig entfärbt — Balken sind dann unsichtbar (transparenter Hintergrund auf transparentem Track).

Folge: Derselbe Report ergibt je nach Bildschirm-Theme des Nutzers einen unterschiedlichen Ausdruck — im Light Mode farbig, im Dark Mode entfärbt. Das widerspricht der eigenen Design-Begründung aus dem Tech Design ("Ausdrucke müssen unabhängig vom Bildschirm-Theme immer lesbar und einheitlich sein").

Reproduktion: Dark Mode aktivieren → /admin → Modul-Übersicht oder Dashboard → Druckvorschau (Strg+P) → Badges/Balken farblos. Dasselbe im Light Mode → farbig. Vorschlag (Umsetzung durch Frontend Developer): Die Print-Regel theme-unabhängig machen (@media print { :root { color-scheme: light } ... } statt nur html.dark) und den *-Holzhammer auf Container-Elemente eingrenzen, damit Signalfarben in beiden Themes gleich gedruckt werden.

BUG-80-2 — AC1 unbelegt — Severity: Low (Prozess) — Priorität 2

Kein Bug im Code, aber das erste und umfangreichste Acceptance Criterion ist weiterhin ungetestet. Nach dem Deploy auf 132 muss ein visueller Durchlauf über /, /search, /mail/[id], /imap, /pop3, /admin/* (alle Tabs), /settings in Dark Mode + Light Mode erfolgen, inkl. 375/768/1440 px und Chrome/Firefox/Safari/Edge. Erst dann ist AC1 abhakbar.


Security Audit

Keine sicherheitsrelevante Änderung. PROJ-80 ist rein CSS/Klassen. Geprüft:

  • Kein neuer Endpunkt, keine Backend-Änderung, keine Änderung an Auth/Tenant-Logik.
  • Der Logo-Container-Fix berührt nur das Styling um <img> herum, nicht das Laden/Validieren des Logos — die PROJ-61-Absicherung (tenant_logo_handlers.go, nosniff) bleibt unangetastet.
  • Print-CSS enthält keine externen Ressourcen (kein url(), kein @import).

Regression

  • npx tsc --noEmit fehlerfrei (Exit 0).
  • ModulesTab.tsx: Summary-Bar referenziert jetzt dieselbe statusColors-Map wie die Badges — behebt einen bestehenden Drift, kein Regressionsrisiko.
  • mail/[id]/page.tsx wird parallel von PROJ-81 angefasst — beide Änderungen liegen in derselben uncommitteten Arbeitskopie und sind konfliktfrei (unterschiedliche Zeilenbereiche: 308 vs. 336-395).
  • PROJ-69 (Admin-Tab-Gruppierung), PROJ-77 (dynamische Tab-Ladung): Tabs nur farblich angefasst, Struktur unverändert.

Produktionsreife: JA, mit Vorbehalt

Keine Critical- oder High-Bugs. Deploy auf 132 ist vertretbar — AC1 muss aber nach dem Deploy im Browser nachgeholt werden, bevor der Status auf Deployed geht. BUG-80-1 kann parallel gefixt werden.


Bugfixes nach QA (Frontend, 2026-08-06)

BUG-80-1 — Wildcard-Reset entfärbt Badges/Balken im Druck — fixed

src/app/globals.css: Der html.dark * { background-color: transparent !important }-Holzhammer ist raus. Stattdessen:

  • Der Neutralisierungs-Reset trifft jetzt nur noch Container-/Text-Elemente (:is(div, section, table, td, span, li, …)) und nimmt Farbträger per :not(.print-chip, .print-chip *, [role="progressbar"], [role="progressbar"] *) aus.
  • Farbträger bekommen eine definierte helle Ersatzfläche (#f2f2f2 + 1px solid #999, schwarzer Text) statt transparent — sie bleiben im Ausdruck als abgesetzter Chip erkennbar. Für Fortschritts-/Auslastungsbalken wird der gefüllte Anteil auf #999 gesetzt, damit Track und Füllung unterscheidbar bleiben (vorher: unsichtbar, weil transparent auf transparent).
  • print-color-adjust: exact verhindert, dass der Browser diese Flächen beim Drucken wegoptimiert.

src/components/admin/ModulesTab.tsx: Die Status-Chips sind einfache <span>-Elemente und wären vom Reset erfasst worden. Sie haben jetzt die Marker-Klasse print-chip. Warum eine Marker-Klasse statt eines Attribut-Selektors: die installierte badge.tsx ist eine ältere shadcn-Version ohne data-slot-Attribut, und src/components/ui/ darf nicht manuell editiert werden. Radix' Progress liefert role="progressbar" von sich aus, daher braucht es dort keinen Marker.

Bewusst nicht umgesetzt / verbleibende Abweichung: Der Vorschlag aus dem QA-Bericht, die Print-Regel komplett theme-unabhängig zu machen, ist mit reinem CSS nicht vollständig erreichbar. Solange die dark-Klasse am <html> hängt, greifen die dark:-Varianten der Tailwind-Klassen weiter; CSS kann sie nicht selektiv zurücknehmen. Der Ausdruck ist jetzt in beiden Themes lesbar und strukturell gleich, aber nicht farbidentisch: im Light Mode drucken Chips in ihrer Signalfarbe, im Dark Mode in der grauen Ersatzfläche. Echte Farbparität würde verlangen, die dark-Klasse per beforeprint/afterprint-Listener temporär zu entfernen — das greift in den next-themes-State ein und wurde hier bewusst nicht ohne Rücksprache gemacht. Falls Farbparität gefordert ist: eigenes Ticket.

BUG-80-2 — AC1 unbelegt — offen, wie abgestimmt

Bleibt für nach dem Deploy. Kein Code-Fix.

Nach den Fixes: npx tsc --noEmit und npm run build fehlerfrei.

Deployment

To be added by /deploy