# 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.
