# Notifications Modul (V1) Letzte Aktualisierung: 2026-03-25 ## Ziel Referenz für das Modul `notifications`: Event-Registrierung, Erzeugungsregeln, Guardrails, Frontend-Vertrag und Testbarkeit. ## Scope v1 - In-App Notifications (Bell + Dropdown), kein E-Mail-Channel. - Core-5 Eventtypen: - `user.created` - `user.deleted` - `user.activated` - `user.deactivated` - `user.assignment_changed` - Dedupe für diese Typen mit 30 Minuten Fenster. ## Modul-Verkabelung Kanonische Quelle: `modules/notifications/module.php` - `event_listeners` - mappt Eventtyp -> Listenerklasse + Methode `handle`. - `ui_slots` - `topbar.right_item`: Bell-Template - `layout.head_style`: Modul-CSS für Topbar + Dropdown - `runtime.component`: Bell-JS (`initNotificationBell`) - `permissions` - `notifications.view` - `authorization_policies` - `NotificationsAuthorizationPolicy` - `layout_context_providers` - liefert `notifications.nav.unread_count` in den Layout-Context - `session_providers` - synchronisiert `module.notifications.unread_count` aus DB ## Event -> Notification Fluss 1. Ein User-Lifecycle-Workflow dispatcht ein Domain-Event (z. B. `user.activated`). 2. Der in `module.php` registrierte Listener verarbeitet das Payload. 3. Listener ruft `NotificationService` auf. 4. `NotificationService` orchestriert Empfaenger/Fan-out und Dedupe-Metadaten. 5. `NotificationRepository` persistiert in `user_notifications`. 6. Bell-Endpunkte lesen tenant-scoped Daten und aktualisieren den Session-`unread_count`. ## Producer-Contract (für Core + andere Module) Stabile Erzeugung erfolgt über: - `MintyPHP\Module\Notifications\Service\NotificationMessage` - `MintyPHP\Module\Notifications\Service\NotificationService::createFromMessage(...)` - `NotificationService::createForTenantUsersFromMessage(...)` - `NotificationService::createForTenantAdminUsersFromMessage(...)` Beispiel (locale-neutral, mit i18n-Key): ```php use MintyPHP\Module\Notifications\Service\NotificationMessage; use MintyPHP\Module\Notifications\Service\NotificationService; $message = NotificationMessage::localized( 'user.created', 'New user: %s', [$displayName], null, [], 'admin/users/edit/' . $uuid, ['user_id' => $userId, 'uuid' => $uuid] ); $notificationService->createForTenantAdminUsersFromMessage( $tenantId, $message, $actorUserId, $allowedAdminSet ); ``` Plain/Fallback-Erzeugung bleibt möglich mit `NotificationMessage::plain(...)`. ## Datenvertrag Notification Persistierter Kernvertrag: - `type` (string) - `title` (string) - `body` (nullable string) - `link` (nullable string) - `data` (nullable JSON) Erweiterung für locale-neutrale Texte: - `data.__message.title_key` (string) - `data.__message.title_params` (array) - `data.__message.body_key` (string, optional) - `data.__message.body_params` (array, optional) Wenn `__message` vorhanden ist, wird der Anzeigetext beim Lesen in der aktuellen Empfaenger-Locale gerendert (Fallback auf persistiertes `title`/`body` bleibt erhalten). Dedupe-Metadaten (intern): - `dedupe_fingerprint` - `dedupe_bucket` - `dedupe_until` ## Guardrails (Server) - Alle Bell-Endpunkte erzwingen: - Login - Ability `notifications.view` - POST-Endpunkte (`mark-read`, `delete`) prüfen CSRF. - Tenant-Scope wird serverseitig aus `current_tenant` erzwungen. - SQL ausschliesslich im Repository (`NotificationRepository`), Service nur Orchestrierung. ## Guardrails (Frontend) - Endpunkte werden mit App-Base-URL gebaut (kein harter Root-Pfad). - Fehlerpfade laufen über zentrale Telemetrie (`warn_once`). - A11y-Klickzeilen im Dropdown sind lokal gestylt, damit globale `[role="button"]` Regeln nicht durchschlagen. - Default-`details/summary`-Chevron wird pro Komponente über `data-summary-chevron="none"` deaktiviert. ## Template/Partial Best Practice - Core-Partials immer über `templatePath('partials/...')` einbinden. - Keine fragilen relativen Tiefenpfade auf Core-Templates verwenden. - Modulinterne Templates bleiben lokal relativ zum Modul. ## CSS Token Best Practice (Module) - Modul-CSS soll auf Core-Variablen aufsetzen (z. B. `--app-color`, `--app-muted-color`, `--app-dropdown-*`, `--app-action-danger-*`). - Modul-spezifische Aliase sind ok (`--app-notification-*`), solange sie auf Core-Tokens mappen. - Harte Hex-Fallbacks nur als Ausnahme; Standard ist tokenbasiert. ## Tests ### Unit - Listener-Tests für alle Core-5 Typen: - Empfaengerregeln - Actor-Exclusion - ungultige/leere Payloads - Service-Tests für Dedupe (created vs suppressed) - Policy-Tests für `notifications.view` ### Integration/Flow - End-to-End für create/delete/activate/deactivate/assignment-change. - Bell-Endpunkte mit Tenant-Scope und Permission-Prüfung. - Bulk-Flows dispatchen konsistent. ### Manueller Smoke 1. Zwei Admins im selben Tenant, einer als Actor. 2. Actor führt User-Aktion aus. 3. Zweiter Admin sieht Bell-Badge + Listeneintrag. 4. Wiederholung innerhalb 30 Minuten erzeugt kein Duplikat.