# P03 · Audit & Notifications (AK-P3 → D-P3) > Interim-UI-Referenz (AD-28). Beide Domänen sind AK-P3-Interim-Screens, rein aus `~/ui` komponiert, VM als > Prop. **Layout = Davids freie Hand** — Listen nennen nur Elemente/Daten/Aktionen/States. Keine der fünf > Dateien importiert `@subframe/core`/Icons; alle Affordances aus `~/ui`-Primitives. ## Audit ### AuditListPage · Audit-Viewer (A2 + Drawer) - **Interim-Screen:** `apps/web/app/components/screens/audit/AuditListPage.tsx` - **Route(n):** `/admin/audit` (`routes/admin/audit.tsx`) — Admin-Host, nicht `app.$tenant`. Permission-Gate `admin.auditTrails.view`. - **Daten (VM):** `AuditListVM` (Rows/Filter über `AuditListFilterState`, `AuditListRow`) — `packages/view-models/src/audit/audit-list.vm.ts` - **Benötigte UI-Elemente (KEIN Layout):** Alert · Button · Drawer · EmptyState · Select (+ Select.Item) · Table (HeaderRow/HeaderCell/Row/Cell) · Tag_Label · TextInput (+ TextInput.Input) · DateField (lokal `../tasks/DateField`). Inhaltselemente: Header (Titel + Subtitle + Export-CTA nur bei `canExport`), Filter-Bar (from/to Datum, Actor-Type-Select, Outcome-Select, Action-Text, TargetType-Text, Apply-Button), Tabelle (Zeit/Actor+Kind-Tag/Action monospace/Subjekt/Outcome/Summary + Detail-Button bei `hasDetail`), Server-Pagination (pageInfo + prev/next), Detail-Drawer (Feld-für-Feld-Diff-Tabelle mit „masked"→Tag „Wert geändert", Metadata `
`, leer→Alert). Icons: keine.
- **Interaktionen (Actions):** `onApply(query: AuditListQuery)` — `AuditListQuery = AuditListFilterState & { page: number }`; getriggert von Filter-Submit, Select-onValueChange, prev/next. Detail-Drawer öffnet rein lokal (`useState openRowId`), kein Action-Callback.
- **States:** Loading = `vm.status !== "ready"` → leerer Marker-Div · Empty = `rows.length === 0` → EmptyState (`emptyHeadlineKey`/`emptyBodyKey`) · Drawer-Empty = kein Diff/Metadata → Alert · Permission-Denied = Route-Level 404 (`verifyUserHasPermission`), kein In-Component-403.
- **Davids Delta (offen):**
- [ ] Überschriebene/geänderte Spec-Punkte: Quick-Filter (Klick auf Actor/Subject/Action) und „Verlauf dieses Objekts"/„Ähnliche Einträge" aus D-P3-Zeile sind im Interim NICHT gebaut — nur Formular-Filter + Diff-Drawer.
- [ ] Neue UI/UX-Entscheidungen: visuelle Unterscheidung der Actor-Kinds (User/Automation/MCP/Agent/Admin) — interim nur neutrales Tag_Label.
- [ ] Subframe-Screen-Link(s):
- [ ] Offene Fragen an Dev: Sollen Quick-Filter denselben `onApply` nutzen (Patch je Feld)?
### AuditExportPage · Audit-Export-Jobs (A2)
- **Interim-Screen:** `apps/web/app/components/screens/audit/AuditExportPage.tsx`
- **Route(n):** `/admin/audit-export` (`routes/admin/audit-export.tsx`) — Admin-Host. (Tenant-Variante `app.$tenant/settings/audit-export.tsx` = `/app/:tenant/settings/audit-export` rendert eigenes Inline-UI, NICHT diesen Interim-Screen.)
- **Daten (VM):** `AuditExportVM` (+ `AuditExportResultVM` als `result`-Prop) — `packages/view-models/src/audit/audit-export.vm.ts`
- **Benötigte UI-Elemente (KEIN Layout):** Alert · Button · Select (+ Select.Item, Helper `SelectWithAny`) · TextInput (+ TextInput.Input) · DateField (lokal `../tasks/DateField`). Inhaltselemente: Header (Titel + Beschreibung mit `maxRows`), ein Formular mit drei Submit-Pfaden (GET csv / GET pdf / POST storage-export), Filterfelder (from/to, tenantId optional, regulatoryScope/actorType/outcome je Select-mit-„Any", purpose Pflicht-Textfeld, format Select), Ergebnis-Panel (Erfolg/Warn-Alert mit Download-Link + Integritäts-Warnung, oder Error-Alert). Icons: keine.
- **Interaktionen (Actions):** KEIN `…Actions`-Typ — reine HTML-Form-Submits gegen die Route (GET streamt CSV/PDF, POST läuft Storage-Export). Einzige Prop neben `vm` ist `result` (Aktions-Ergebnis, null = noch nichts abgeschickt).
- **States:** Loading = `vm.status !== "ready"` → leerer Marker-Div · Ergebnis: `result.ok===false` → Error-Alert (`errorKey`); `result.ok` → Success/Warning-Alert (Row-Count, Ablauftage, Download-Link, ggf. Integritäts-Warnung); `result===null` → kein Panel · Kein Empty-State · Permission-Denied = Route-Level 404.
- **Davids Delta (offen):**
- [ ] Überschriebene/geänderte Spec-Punkte: D-P3 sieht Export-Job-**Liste** + `/new` + `/:id` (A2) vor — Interim ist ein Single-Formular ohne Job-Liste/Detail-Route.
- [ ] Neue UI/UX-Entscheidungen: Zweck-Pflichtfeld (`purpose`) Prominenz; Format-Auswahl CSV/PDF vs. Spec CSV/NDJSON.
- [ ] Subframe-Screen-Link(s):
- [ ] Offene Fragen an Dev: Job-Liste/`:id`-Status als eigene D-P3-Screens nachziehen oder Single-Form-Ansatz beibehalten?
## Notifications
### NotificationCenterPage · Notification-Center (A2 + Drawer)
- **Interim-Screen:** `apps/web/app/components/screens/notifications/NotificationCenterPage.tsx`
- **Route(n):** `/app/:tenant/notifications` (`routes/app.$tenant/notifications.tsx`) — Route hostet SSE-Subscription (`useNotificationsStream`).
- **Daten (VM):** `NotificationCenterVM` (Items über `NotificationCenterItemVM`) — `packages/view-models/src/notifications/notification-center.vm.ts`
- **Benötigte UI-Elemente (KEIN Layout):** Alert · BadgeFeedback · Button · EmptyState · Tabs (+ Tabs.Item). Inhaltselemente: Header (Titel + Diagnostics-Subtitle + sr-only Live-Region für `connectionStatusKey`), Tabs Alle/Ungelesen mit Counts, Bulk-Clear-Button (nur bei `unreadCount>0`), Notification-Cards (Message als Deeplink-Link oder Text, Kategorie·Channel·Zeit-Metazeile, Status-Badge farbcodiert READ/FAILED/PENDING, Bounce-Alert bei `bounceKey` mit `lastErrorRedacted`, Mark-Read-Button bei ungelesen). Icons: keine.
- **Interaktionen (Actions):** `onMarkRead(deliveryId: string)` · `onMarkAllRead()`. Tab-Umschaltung Alle/Ungelesen ist lokal (`useState`).
- **States:** Loading = `vm.status !== "ready"` → leerer Marker-Div · Empty = `totalCount===0` → EmptyState (`emptyHeadlineKey`/`emptyBodyKey`) · Unread-Empty = gefilterte Liste leer → EmptyState (`unreadEmptyKey`) · Permission-Denied = Route-Level (Auth-Middleware), kein In-Component-403.
- **Davids Delta (offen):**
- [ ] Überschriebene/geänderte Spec-Punkte: D-P3 nennt Prioritäts-Marker + Mark-**unread** — Interim hat nur Mark-read (kein Priority-Marker, kein Unread-Zurücksetzen).
- [ ] Neue UI/UX-Entscheidungen: Bounce-Klartext-Darstellung („Emails an dich kommen aktuell nicht an") als Error-Alert je Card.
- [ ] Subframe-Screen-Link(s):
- [ ] Offene Fragen an Dev: Priority-Marker-Datenfeld im VM ergänzen?
### NotificationsDrawer · Notification-Center Topbar-Drawer (A2 + Drawer)
- **Interim-Screen:** `apps/web/app/components/screens/notifications/NotificationsDrawer.tsx`
- **Route(n):** n/a — Shell-Level-Komponente (globaler Drawer, gehostet von `apps/web/app/components/layouts/NavBar.tsx`; NavBar besitzt SSE-Subscription + Open-State).
- **Daten (VM):** `NotificationCenterItemVM` (kein eigener VM; Items als Prop, max 10) — `packages/view-models/src/notifications/notification-center.vm.ts`
- **Benötigte UI-Elemente (KEIN Layout):** BadgeFeedback · Button · Drawer (+ Drawer.Content) · EmptyState. Inhaltselemente: Header (Titel + Close-Button), Item-Liste (max 10; Message als Deeplink oder Text, ungelesen fett, Zeitstempel, „Ungelesen"-Badge bei nicht gelesen), „Alle anzeigen"-Link (`allHref`). Icons: keine.
- **Interaktionen (Actions):** KEIN `…Actions`-Typ — Props `open`, `items`, `allHref`, `onClose()`. Deeplink-Klicks folgen validierten `deeplinkHref`; „Alle anzeigen" navigiert zu `allHref`.
- **States:** Empty = `visibleItems.length===0` → EmptyState (`drawer.empty` + `emptyHelper`) · kein eigenes Loading/Permission-Denied (Shell steuert Sichtbarkeit).
- **Davids Delta (offen):**
- [ ] Überschriebene/geänderte Spec-Punkte: kein Mark-read/Bulk-Clear im Drawer (nur „Alle anzeigen" → Center).
- [ ] Neue UI/UX-Entscheidungen: Verhältnis Bell-Slot (D-D0a) ↔ Drawer-Öffnung; 10-Item-Cap.
- [ ] Subframe-Screen-Link(s):
- [ ] Offene Fragen an Dev: Soll der Drawer Inline-Mark-read bekommen oder bewusst read-only bleiben?
### NotificationSettingsPage · Notification-Settings (A6)
- **Interim-Screen:** `apps/web/app/components/screens/notifications/NotificationSettingsPage.tsx`
- **Route(n):** `/app/:tenant/settings/notifications` (`routes/app.$tenant/settings/notifications.tsx`) — Actions fetcher-backed.
- **Daten (VM):** `NotificationSettingsVM` (Channel-Typ `NotificationSettingChannel`) — `packages/view-models/src/notifications/notification-settings.vm.ts`
- **Benötigte UI-Elemente (KEIN Layout):** Alert · Switch (+ lokaler `ChannelToggle`-Wrapper). Inhaltselemente: Header (Heading + Description), Save-State-Alert, Master-Karte „alle Emails aus" (Switch), pro Modul-Gruppe eine Karte mit Modul-Label + je Kategorie In-App/Email-Toggle. Icons: keine.
- **Interaktionen (Actions):** `onTogglePreference({ category, channel, enabled })` · `onSetEmailMaster(disableAllEmails: boolean)` (Master-Switch schreibt alle Kategorien). Zusätzliche Prop `saveState: { ok; errorText? } | null`.
- **States:** Loading = `vm.status !== "ready"` → leerer Marker-Div · Save = `saveState.ok` → Success-Alert / sonst Error-Alert (`errorText`) · `saveState===null` → nichts · kein Empty-State (Registry liefert immer Gruppen) · Permission-Denied = Route-Level.
- **Davids Delta (offen):**
- [ ] Überschriebene/geänderte Spec-Punkte: D-P3 nennt Priority-Selektor je Event-Typ — Interim hat nur In-App/Email-Toggles, keinen Priority-Selektor.
- [ ] Neue UI/UX-Entscheidungen: Master-Toggle-Klartext „betrifft auch zukünftige Event-Typen".
- [ ] Subframe-Screen-Link(s):
- [ ] Offene Fragen an Dev: Priority pro Kategorie im VM/Registry ergänzen?