From 0a02bd711273693aa14096b168703a4a91a79730 Mon Sep 17 00:00:00 2001 From: sysops Date: Thu, 6 Aug 2026 18:00:41 +0200 Subject: [PATCH] =?UTF-8?q?feat(PROJ-80,PROJ-81):=20Dark=20Mode=20Vervolls?= =?UTF-8?q?t=C3=A4ndigung=20+=20Anhang-Online-Vorschau?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 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 Claude-Session: https://claude.ai/code/session_01WapWkrQusDuBMhaN8WyuXB --- features/INDEX.md | 5 +- .../PROJ-80-dark-mode-vervollstaendigung.md | 207 +++++++++++++ features/PROJ-81-anhang-online-vorschau.md | 252 ++++++++++++++++ .../PROJ-82-print-farbparitaet-dark-mode.md | 38 +++ src/app/globals.css | 66 ++++ src/app/mail/[id]/page.tsx | 40 ++- src/components/admin/ModulesTab.tsx | 22 +- src/components/admin/tabs/TenantLDAPTab.tsx | 3 +- src/components/admin/tabs/TenantsTab.tsx | 2 +- .../mail/AttachmentPreviewDialog.tsx | 284 ++++++++++++++++++ src/data/features.ts | 3 + 11 files changed, 899 insertions(+), 23 deletions(-) create mode 100644 features/PROJ-80-dark-mode-vervollstaendigung.md create mode 100644 features/PROJ-81-anhang-online-vorschau.md create mode 100644 features/PROJ-82-print-farbparitaet-dark-mode.md create mode 100644 src/components/mail/AttachmentPreviewDialog.tsx diff --git a/features/INDEX.md b/features/INDEX.md index 8a5a405..0fb6b07 100644 --- a/features/INDEX.md +++ b/features/INDEX.md @@ -95,7 +95,10 @@ | PROJ-77 | Admin-Tab-Bundle-Optimierung (dynamic import statt 19 statische Imports) | Deployed | [PROJ-77](PROJ-77-admin-tabs-dynamic-import.md) | 2026-08-05 | | PROJ-78 | SearchResultsTable re-rendert bei jedem Tastenanschlag im Suchfeld | Deployed | [PROJ-78](PROJ-78-search-results-rerender-perf.md) | 2026-08-05 | | PROJ-79 | Frontend-Dependency-Major-Upgrades (eslint, lucide-react, tailwindcss+tailwind-merge, typescript, @types/node) | Deployed | [PROJ-79](PROJ-79-dependency-major-upgrades.md) | 2026-08-06 | +| PROJ-80 | Dark Mode Vervollständigung (Konsistenz + Default/Persistenz) | In Review | [PROJ-80](PROJ-80-dark-mode-vervollstaendigung.md) | 2026-08-06 | +| PROJ-81 | Anhang-Online-Vorschau (PDF, Bilder) | In Review | [PROJ-81](PROJ-81-anhang-online-vorschau.md) | 2026-08-06 | +| PROJ-82 | Print-Farbparität zwischen Hell- und Dark-Mode-Ausdrucken | Planned | [PROJ-82](PROJ-82-print-farbparitaet-dark-mode.md) | 2026-08-06 | -## Next Available ID: PROJ-80 +## Next Available ID: PROJ-83 diff --git a/features/PROJ-80-dark-mode-vervollstaendigung.md b/features/PROJ-80-dark-mode-vervollstaendigung.md new file mode 100644 index 0000000..cd41f89 --- /dev/null +++ b/features/PROJ-80-dark-mode-vervollstaendigung.md @@ -0,0 +1,207 @@ +# 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 `` — 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` `` + `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:21` — `bg-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: + +```css +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 `` 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 ``-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 `` 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_ diff --git a/features/PROJ-81-anhang-online-vorschau.md b/features/PROJ-81-anhang-online-vorschau.md new file mode 100644 index 0000000..897482b --- /dev/null +++ b/features/PROJ-81-anhang-online-vorschau.md @@ -0,0 +1,252 @@ +# PROJ-81: Anhang-Online-Vorschau (PDF, Bilder) + +## Status: In Review +**Created:** 2026-08-06 +**Last Updated:** 2026-08-06 + +## Kontext +Mail-Anhänge lassen sich in `/mail/[id]` aktuell nur herunterladen (`downloadMailAttachment`), keine Online-Ansicht im Browser. PROJ-81 ergänzt eine Vorschau direkt im Frontend, damit User Anhänge nicht erst lokal öffnen müssen. + +## Dependencies +- Baut auf PROJ-7 (E-Mail-Ansicht) auf — Anhang-Liste existiert bereits in `/mail/[id]` + +## User Stories +- Als User will ich einen PDF-Anhang direkt im Browser ansehen können, ohne ihn herunterzuladen. +- Als User will ich Bild-Anhänge (jpg/png/gif) als Vorschau sehen, bevor ich sie herunterlade. +- Als User will ich die Vorschau in einem Dialog/Overlay öffnen, ohne die Mail-Ansicht zu verlassen. +- Als Admin/Auditor will ich, dass Vorschau nur für Anhänge funktioniert, auf die der jeweilige Tenant/User laut bestehender Zugriffsregeln berechtigt ist (keine neue Sicherheitslücke). + +## Acceptance Criteria +- [ ] Klick auf Anhang öffnet Vorschau als Modal/Dialog über der aktuellen Mail-Ansicht +- [ ] PDF-Anhänge werden inline gerendert (Browser-natives PDF-Rendering oder eingebetteter Viewer) +- [ ] Bild-Anhänge (jpg, png, gif, webp) werden als Bildvorschau angezeigt +- [ ] Nicht unterstützte Dateitypen (inkl. Office-Dokumente wie docx/xlsx) zeigen Hinweis "Keine Vorschau verfügbar" + Download-Button bleibt erhalten +- [ ] Download-Option bleibt im Dialog zusätzlich verfügbar (Vorschau ersetzt Download nicht) +- [ ] Vorschau respektiert bestehende Tenant-Isolation/Zugriffsrechte (kein direkter öffentlicher Link auf Rohdatei) +- [ ] Große Anhänge (Performance-Grenze definieren, z.B. > 20 MB) zeigen Warnhinweis statt automatischem Laden + +## Edge Cases +- Sehr große PDF/Bild-Dateien (>20 MB) → Warnhinweis statt automatischer Vorschau, User muss Laden explizit bestätigen +- Passwortgeschützte/verschlüsselte PDFs → Vorschau schlägt fehl, Fehlermeldung + Download-Fallback +- Beschädigte/korrupte Anhänge → Fehlermeldung statt Absturz des Dialogs +- Sehr viele Anhänge in einer Mail → Performance beim Öffnen mehrerer Vorschauen nacheinander +- Anhang wurde per DSGVO-Löschersuchen (PROJ-50) bereits entfernt → Vorschau zeigt "nicht mehr verfügbar" statt Fehler + +## Technical Requirements (optional) +- Performance: PDF/Bild-Vorschau < 2s Ladezeit bei normaler Dateigröße +- Security: Vorschau-Endpunkt muss denselben Auth-/Tenant-Check wie bestehender Download-Endpunkt durchlaufen +- Browser Support: Chrome, Firefox, Safari, Edge + +--- + + +## Tech Design (Solution Architect) + +### A) Komponentenstruktur (visuell) + +``` +Mail-Ansicht (/mail/[id]) ++-- Anhang-Liste (bestehend) +| +-- Anhang-Zeile +| +-- "Vorschau"-Button (NEU) +| +-- "Herunterladen"-Button (bestehend, bleibt) ++-- Vorschau-Dialog (NEU, Overlay über aktueller Seite) + +-- Titelzeile (Dateiname, Schließen-Button) + +-- Inhalt (je nach Typ): + | +-- PDF-Ansicht (eingebetteter Viewer) + | +-- Bild-Ansicht (Vollbild-Bild, zoombar) + | +-- "Keine Vorschau verfügbar"-Hinweis (Fallback, u.a. für Office-Dokumente) + +-- Ladezustand (Spinner bei großer Datei) + +-- Warnhinweis bei sehr großen Dateien ("trotzdem laden?") + +-- "Herunterladen"-Button (bleibt zusätzlich verfügbar) +``` + +### B) Datenfluss (Klartext, kein neues Datenmodell) + +- Vorschau nutzt denselben bestehenden Anhang-Endpunkt wie der heutige Download (`/api/mails/{id}/attachments/{index}`), der bereits Tenant-/Zugriffsprüfung durchläuft — kein neuer öffentlicher Link, keine neue Sicherheitsfläche. +- PDF und Bilder: Browser stellt Datei direkt dar, keine Serververarbeitung nötig. +- Kein neuer Datenbank-Eintrag nötig — Vorschau ist ein reiner Anzeige-Vorgang, keine dauerhaft gespeicherte Information. +- Kein Server-seitiger Konvertierungsschritt (Office-Dokumente bewusst außerhalb des Scopes, siehe unten). + +### C) Tech-Entscheidungen (Begründung) + +- **Vorschau als Modal statt neue Seite/Route:** User bleibt im Kontext der Mail, kein Verlust der aktuellen Ansicht/Scroll-Position (laut Klärung des Nutzers gewünscht). +- **Bestehenden Anhang-Endpunkt wiederverwenden statt neuen zu bauen:** Zugriffsrechte (Tenant-Isolation, Auth) sind dort bereits korrekt implementiert — Wiederverwendung vermeidet doppelte Sicherheitslogik und Inkonsistenzrisiko. +- **Office-Dokumente (docx/xlsx) bewusst außerhalb des Scopes:** Nutzer hat Scope auf PDF+Bilder reduziert. Damit entfällt der größte Aufwandstreiber (Server-seitige Konvertierung, neue Systemabhängigkeit). Reines Frontend-Feature ohne neue Backend-Logik. +- **Größenlimit + Warnhinweis statt hartem Blocken:** Verhindert, dass ein einzelner sehr großer Anhang den Browser blockiert, lässt dem User aber die Wahl, ihn trotzdem zu laden. + +### D) Abhängigkeiten (Pakete) + +- PDF-Viewer-Bibliothek im Frontend (Anzeige im Dialog, ohne Download). Bilder benötigen keine zusätzliche Bibliothek (natives ``). +- Keine neuen Server-/Systemabhängigkeiten. + +## Implementation Notes (Frontend, 2026-08-06) + +Neue Komponente: `src/components/mail/AttachmentPreviewDialog.tsx` +Angebunden in `src/app/mail/[id]/page.tsx` (`AttachmentRow`): pro Anhang erscheint ein "Vorschau"-Button, aber nur wenn der Typ vorschaufähig ist; "Herunterladen" bleibt unverändert daneben und zusätzlich im Dialog. + +Keine neue API-Funktion nötig — der Dialog nutzt das bestehende `downloadMailAttachment(mailId, index)` und damit denselben Endpunkt inkl. bestehender Auth-/Tenant-Prüfung. `src/lib/api/index.ts` musste nicht ergänzt werden. + +### Security-Umsetzung (PROJ-61-Kontext) +Abweichung vom Tech Design in der Umsetzung, bewusst strenger: +- Der Anhang wird per authentifiziertem `fetch` als Blob geholt, **nicht** per direkter Navigation auf die Anhang-URL. +- Der vom Server gelieferte `Content-Type` wird **nicht** zum Rendern verwendet. Der Blob wird clientseitig mit einem erzwungenen MIME-Typ aus einer festen Whitelist neu verpackt. Eine als `rechnung.pdf` getarnte HTML-Datei kann dadurch nicht als aktives Dokument im App-Origin ausgeführt werden. +- Whitelist: `pdf` → `application/pdf`; `jpg/jpeg/png/gif/webp/bmp` → jeweiliger Bildtyp. **SVG ist bewusst ausgeschlossen** (kann Skripte enthalten — genau der PROJ-61-Vektor). +- PDF rendert in `