forked from fa/breadcrumb-the-shire
Most German docs were written with `ue`/`ae`/`oe`/`ss` ASCII fallbacks (`fuer`, `aenderung`, `koennen`, `gemaess`) — relic from older toolchain constraints. Replaced 237 occurrences across 38 .md files with proper umlauts and ß so the docs read naturally. Substitutions are constrained to plain prose: fenced code blocks (```...```), inline code spans (`...`), markdown link targets `[..](..)`, and HTML comments are preserved verbatim. Path references like `docs/howto-erste-aenderung.md` keep their original (umlaut-replaced) filenames — only the surrounding prose is normalised. Tool: bin/fix-md-umlauts.py (idempotent — no-op on already-clean files). Re-runnable if ASCII fallbacks creep back in via copy-paste. Doc-link-check + doc-drift-check stay green. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
4.9 KiB
4.9 KiB
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.createduser.deleteduser.activateduser.deactivateduser.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.
- mappt Eventtyp -> Listenerklasse + Methode
ui_slotstopbar.right_item: Bell-Templatelayout.head_style: Modul-CSS für Topbar + Dropdownruntime.component: Bell-JS (initNotificationBell)
permissionsnotifications.view
authorization_policiesNotificationsAuthorizationPolicy
layout_context_providers- liefert
notifications.nav.unread_countin den Layout-Context
- liefert
session_providers- synchronisiert
module.notifications.unread_countaus DB
- synchronisiert
Event -> Notification Fluss
- Ein User-Lifecycle-Workflow dispatcht ein Domain-Event (z. B.
user.activated). - Der in
module.phpregistrierte Listener verarbeitet das Payload. - Listener ruft
NotificationServiceauf. NotificationServiceorchestriert Empfaenger/Fan-out und Dedupe-Metadaten.NotificationRepositorypersistiert inuser_notifications.- 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\NotificationMessageMintyPHP\Module\Notifications\Service\NotificationService::createFromMessage(...)NotificationService::createForTenantUsersFromMessage(...)NotificationService::createForTenantAdminUsersFromMessage(...)
Beispiel (locale-neutral, mit i18n-Key):
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_fingerprintdedupe_bucketdedupe_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_tenanterzwungen. - 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 überdata-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
- Zwei Admins im selben Tenant, einer als Actor.
- Actor führt User-Aktion aus.
- Zweiter Admin sieht Bell-Badge + Listeneintrag.
- Wiederholung innerhalb 30 Minuten erzeugt kein Duplikat.