20 KiB
Boilerplate-Abgleich — M22 Notifications
| Feld | Wert |
|---|---|
| Modul-ID | M22 (Notifications) |
| Modul-Name | Drei-Kanal-Notifications (In-App / Email / Mobile-Push) |
| Milestones | M115 (Notification-Center + Lese-Status), M116 (User-Settings: Kanal + Stille-Stunden + Geräte), M117 (Kategorien + Bündelung + Anti-Spam + Versand-Pipeline) |
| Block | Block 5 (Kommunikation), §22 |
| Service-Layer-Querschnitt | M011 (notify()) — Boilerplate-Foundation deckt das Fundament |
| Reuse-Level | MEDIUM |
Quellen verifiziert (eigene Reads/Greps, nicht nur Inventur): Detail-Spec
_bmad-output/specs/M22-notifications.md, Pflichtenheftblock5.md §22, die 3 PLAN.md (M115-M117), AGENTS.md (DoD A-F), INTEGRATION-CONTRACTS.md (§1 Sidebar, §7 Notification-Wiring), Boilerplate-Inventur (foundation/data-layer/apps-infra/ui-shell) UND direkte Reads der Boilerplate-Dateien (Prisma-Schema, db/foundation/realtime/mail-Pakete, API-Routes, Worker-Job, Web-Routes/Components).
Kern-Befund (eine Zeile)
Die Boilerplate hat einen fast vollständigen vertikalen In-App-Notification-Slice (DB-CRUD → SSE-Stream-API → SSE-Client mit Reconnect → Notification-Center-Component → Tenant-Route + minimale Settings-Route) plus ein Mobile-Push-Fundament (DeviceToken-Modell, Register/Revoke-Routes, provider-pluggable Push-Fan-out-Worker, Capacitor-Push-Registration) plus BullMQ-notifications-Queue, @toolctrl/mail + React-Email-Templates und sogar eine Novu-Anbindung. Aber: Das Datenmodell ist eine schlanke Delivery/Diagnostics-Sicht (NotificationDelivery) — es fehlt das reiche M22-Notification-Aggregat (title/body/deeplink/actor/priority/bundle), die PUSH-Channel-Enum-Werte, Quiet-Hours, Bundle-Window/Sliding-Window, Priority und die ganzen Settings-/Devices-/Bündel-UI-Flächen.
Übernehmbar aus Boilerplate
| Boilerplate-Pfad | Was | Wie nutzbar |
|---|---|---|
/Users/raffaelsheikh/Documents/GitHub/boilerplate/packages/foundation/src/notifications.ts |
notify(), notifyMany(), bundleNotifications(), isNotificationEnabled(), assertI18nKey(), Typen NotificationInput/Preference/Bundle — DB-/framework-agnostisches Querschnitt-Primitiv mit Preference-Check, sourceKey-Upsert (Idempotenz), Metadata-Sanitizing/Redaction |
als-vorlage — exakt der M011-notify()-Einstiegspunkt aus INTEGRATION-CONTRACTS §7. Muss aber um PUSH-Channel, priority, Quiet-Hours-Resolver und echte Bundle-Window-Logik erweitert werden (heute nur statisches Gruppieren ohne Zeitfenster). |
/Users/raffaelsheikh/Documents/GitHub/boilerplate/packages/db/src/notifications.server.ts |
Vollständige Server-Helper: createNotificationDelivery (upsert/idempotent per sourceKey), markInAppNotificationDeliveryRead, markNotificationDeliveryStatus, getNotificationDeliveryDiagnostics, getNotificationStreamSnapshot (Cursor-Stream, 50/Seite), upsert/getUserNotificationPreferences, Redaction (redactNotificationDiagnosticText) |
anpassen — Lese-Pfade (Stream-Snapshot, mark-read, Preferences) sind 1:1 nutzbar. Schreib-Pfad braucht die neuen M22-Felder (title/body/deeplink/actor/priority/bundle_id). PUSH-Channel und Priority-Threshold fehlen. |
/Users/raffaelsheikh/Documents/GitHub/boilerplate/prisma/schema/notifications.prisma |
Modelle NotificationDelivery, UserNotificationPreference, DeviceToken + Enums NotificationDeliveryStatus, NotificationPreferenceChannel |
anpassen — siehe „Vorhandene Daten-Modelle". DeviceToken ist quasi 1:1 die M22-NotificationDevice. Die anderen beiden sind Fundament, brauchen aber Felderweiterung + neue Modelle (Bundle, QuietHours, reiches Notification-Aggregat). |
/Users/raffaelsheikh/Documents/GitHub/boilerplate/packages/db/src/device-tokens.server.ts |
registerDeviceToken (idempotent per token), revokeDeviceToken, revokeDeviceTokensForUser (Logout-Hook), listActiveDeviceTokens, findDeviceTokenByToken, parseDevicePlatform/parsePushProvider/defaultPushProviderFor |
1:1 — deckt Block H Token-Registrierung + Auto-Revoke (Logout/Token-ungültig) komplett ab. Nur Bulk-Revoke-UI und last_seen_at-Bump bei Push-Erfolg ergänzen. |
/Users/raffaelsheikh/Documents/GitHub/boilerplate/apps/api/src/routes/devices-routes.ts |
POST /v1/devices/register + POST /v1/devices/revoke (Validierung, Längen-Limits, Klartext-Fehler) |
anpassen — entspricht Spec-Endpoint /api/v1/notifications/mobile-device. Auth ist heute service-credential mit userId/tenantId aus dem Body; für M22-Self-Scope (eingeloggter User) muss gegen den authentifizierten Principal verifiziert werden. |
/Users/raffaelsheikh/Documents/GitHub/boilerplate/apps/worker/src/jobs/push-notification.ts |
dispatchPushNotification() — provider-pluggabler Fan-out (APNs/FCM/WebPush via Seam), Auto-Revoke bei Unregistered/InvalidRegistration/BadDeviceToken, Concurrency-Limit, Token-Redaction in Logs |
1:1 (Orchestrierung) — pure, testbar. Block J „Push-Sender" + Token-Invalidierungs-Handling (Block H). Provider-SDK-Adapter (firebase-admin/node-apn) werden in apps/worker/src/index.ts injiziert (Operator-Setup). |
/Users/raffaelsheikh/Documents/GitHub/boilerplate/packages/realtime/src/events.ts + client.ts + server.ts + guards.ts |
Realtime-SSE-Schicht: REALTIME_EVENT_NAMES (notification.snapshot/notification.update/keepalive/error), validateRealtimeEvent (Secret-Key-Reject, Allow-list, Cursor), Subscriber-Client mit Exponential-Backoff-Reconnect, Scope-Guards |
1:1 — deckt Block B.1 „Real-time-Update über SSE" + Polling-Fallback (im Client). Nur notification.update-Push (Live-Toast) muss vom Sender aus emittiert werden. |
/Users/raffaelsheikh/Documents/GitHub/boilerplate/apps/api/src/routes/notifications-routes.ts |
GET /v1/notifications/stream — SSE-Endpoint: Scope-Normalisierung, Workspace-Revalidation, Snapshot-Push, Keepalive, harte Security-Header |
anpassen — Stream-Fundament steht. Heute reiner Snapshot+Keepalive (kein dauerhafter Live-Push pro neuer Notification); für echtes Live-Center muss ein Pub/Sub-Fan-out (shared-store/Redis) ergänzt werden. |
/Users/raffaelsheikh/Documents/GitHub/boilerplate/apps/web/app/components/notifications/NotificationCenter.tsx |
Fertige In-App-Center-Component: SSE-Subscribe + Reconnect, Merge/Dedupe/Sort, Mark-as-read-Form, Empty-State, i18n, Status-Badges, Last-Error-Redaction | als-vorlage — gute Basis für M115, aber: zeigt heute Delivery-Diagnostik (Status/Attempts/Channel) statt der M22-Center-Felder (title/body/Actor-Avatar/Deeplink/Priority/Bundle-Count, Tabs Alle/Ungelesen/Markiert, Filter, Suche, Bulk). Darf NICHT direkt aus Routes importiert werden — über Adapter (packages/ui-hivegrid/src/adapters/, AGENTS.md A.4). |
/Users/raffaelsheikh/Documents/GitHub/boilerplate/apps/web/app/routes/app.$tenant/notifications.tsx (+ notifications/stream.tsx) |
Tenant-Route mit Loader (Diagnostics→VM), Action (mark-read + CSRF), Stream-URL-Bridge | als-vorlage — exaktes RR7-Loader/Action-Muster für M115. Felder/VM müssen aufs reiche M22-Modell umgebaut werden. |
/Users/raffaelsheikh/Documents/GitHub/boilerplate/apps/web/app/routes/app.$tenant/settings/notifications.tsx |
Settings-Route: Preference-Toggle pro Kanal/Kategorie (IN_APP/EMAIL), CSRF-Action, Loader | als-vorlage — Grundgerüst für M116-Präferenzen. Sehr schmal (3 Kategorien hartkodiert, kein PUSH, kein Priority-Threshold, kein Master-Toggle, keine Modul-Gruppierung, keine Quiet-Hours, keine Geräte). |
/Users/raffaelsheikh/Documents/GitHub/boilerplate/apps/web/app/components/layouts/buttons/NotificationsButton.tsx + .../sidebars/shadcn/nav-actions.tsx + .../icons/IconNotifications.tsx |
Glocken-Button in der Header-Action-Leiste (feature-gated appConfiguration.notifications.enabled), verlinkt auf Tenant-/Admin-Notifications |
anpassen — Block B.1 „Glocke in Top-Bar". Unread-Count-Badge + Tab-Title-Update fehlen und müssen ergänzt werden. |
/Users/raffaelsheikh/Documents/GitHub/boilerplate/packages/mail/src/index.ts (+ templates-react/, mailersend.ts, queue.ts) |
@toolctrl/mail: MailerSend-Provider (EU-Region nutzbar), QueuedMailProvider, React-Email-Render (renderTransactionalEmail), Brand-Defaults (buildBrand/withBrandDefaults) |
anpassen — Block B.2 Email-Versand + Branding. EU-Stack (LP-13). Tenant-Sending-Domain-Anbindung (Modul 9) + Bounce-Pause-Logik + Notification-spezifische Templates fehlen. |
/Users/raffaelsheikh/Documents/GitHub/boilerplate/packages/email-templates/src/index.ts |
React-Email-Template-Engine (renderEmail → {html,text}), Layout-Primitives, Theme-Tokens |
als-vorlage — Muster für die Notification-Email-Templates (Lead/Task/Termin/Auth/Subscription) aus Locale-Files (LP-12). |
Worker-Infra: apps/worker/src/queues.ts (defaultWorkerQueueNames enthält notifications), handlers.ts (HandlerDependencies DI-Seam für notification), outbox/event-router.ts (notification.-Prefix → Queue), @toolctrl/queue buildNotificationJob/enqueueJob/queueNames.notifications |
BullMQ-notifications-Queue + Transactional-Outbox-Routing + Retry/Backoff-Fundament |
1:1 / anpassen — Block J Versand-Pipeline (Producer→Queue→Worker→Status-Tracker), Retry-Logik (3x Backoff) ist BullMQ-Standard. Bundle-Manager + Quiet-Hours-Resolver + Pro-User-Push-Limit (10/h) sind neu zu bauen. |
/Users/raffaelsheikh/Documents/GitHub/boilerplate/apps/mobile/src/pushRegistration.ts + capacitor.config.ts (Plugin push-notifications, local-notifications) |
Capacitor-Push-Token-Registration gegen /v1/devices/register, idempotenter Boot |
1:1 — Block H Registrierungs-Flow (Modul 36). Apple-Compliance-Pre-Prompt-Teaser-Screen (LP-8) fehlt und ist im Mobile-Modul zu bauen. |
/Users/raffaelsheikh/Documents/GitHub/boilerplate/apps/web/app/modules/notifications/services/.server/NotificationService.ts |
Modul-Service mit BullMQ-notifications-Queue-Enqueue + Novu-API-Anbindung (novuApiFetch, NOTIFICATIONS_NOVU_API_KEY), Subscriber/Message/Integration-DTOs |
als-vorlage / anpassen — bestätigt Spec-Wahl „Novu als Abstraktions-Schicht" (Block B.3). ACHTUNG: .server.ts-Re-Export ist projektverboten (User-Memory) → Component-Extraktion statt Re-Export. |
Vorhandene Daten-Modelle
Alle in /Users/raffaelsheikh/Documents/GitHub/boilerplate/prisma/schema/notifications.prisma:
NotificationDelivery (:14)
- Felder:
id,createdAt,updatedAt,tenantId?,userId?,channel(Enum),category(String),messageKey,sourceKey?,status,attemptsMade,providerMessageId?,lastError?,payloadMetadata Json?,sentAt?,readAt? - Indizes:
(tenantId,userId,status),(tenantId,channel,category),(status,updatedAt),(providerMessageId);@@unique([tenantId,userId,channel,sourceKey])(Idempotenz-Upsert) - Lücke vs. Spec-
Notification: keintitle,body,deeplink,event_type,event_subject_type/id,actor_user_id,actor_kind,priority,dismissed_at,bundle_id,delivery_status pro Kanal(hier 1 Row pro Channel statt 1 JSONB). Ist eher eine pro-Kanal-Delivery-Zeile als das reiche Notification-Aggregat der Spec.
UserNotificationPreference (:41)
- Felder:
id,tenantId,userId,channel(Enum),category(String),enabled(Bool);@@unique([tenantId,userId,channel,category]) - Lücke vs. Spec-
NotificationPreference: keinpriority_threshold, kein PUSH-Channel (Enum nur IN_APP/EMAIL),category≠event_type-Katalog, kein Rolle-Default-Seeding.
DeviceToken (:66)
- Felder:
id,createdAt,updatedAt,userId,tenantId?,platform(ios|android|web),token @unique,pushProvider(fcm|apns|webpush),appVersion?,lastSeenAt,revokedAt? - Indizes:
(userId,tenantId),(tenantId,platform),(revokedAt) - Sehr nah an Spec-
NotificationDevice— fehlt nurdevice_label,os_version(separat von appVersion).device_kind=platform,revoked_at=revokedAt,last_seen_at=lastSeenAt. Quasi 1:1.
Enums
NotificationDeliveryStatus:PENDING | SENT | FAILED | SKIPPED | READ(passt grob, Spec hat granulareredelivery_statuspro Kanal als JSONB — z.B.bounced,skipped:user_preference,failed:smtp_timeout)NotificationPreferenceChannel:IN_APP | EMAIL—PUSHfehlt (obwohl DeviceToken + Push-Worker existieren). Konzeptioneller Bruch zwischen Preference-Enum und Push-Fähigkeit.
FEHLENDE Modelle komplett (Spec Block A): Notification (reiches Aggregat), NotificationBundle (Anti-Spam), NotificationQuietHours (Ruhezeiten). Diese existieren in der Boilerplate nicht.
Lücken / komplett neu zu bauen
Daten-Layer (neu):
Notification-Aggregat mit title/body/deeplink/event_type(Enum)/event_subject/actor/actor_kind/priority/dismissed_at/bundle_id/metadata +delivery_statusJSONB pro Kanal (Spec Block A)NotificationBundle-Modell (event_type_group, count, subject_summary, bundle_window_seconds, closed_at) — kein Pendant in BPNotificationQuietHours-Modell (weekday, start/end, urgent_override, enabled) — kein Pendant in BPPUSHals dritterNotificationPreferenceChannel-Enum-Wert +priority+priority_threshold- M22-Index
(tenant_id, recipient_user_id, created_at DESC)und Unread-Count-Index — die BP-Indizes sind delivery-zentriert (status/channel/category), nicht ideal für das Center-Default-Query
Domänen-Logik (neu / erweitern):
- Bundle-Manager mit Sliding-5-Min-Window (BP
bundleNotificationsgruppiert nur statisch, kein Zeitfenster, kein Counter, kein 100-Max, kein Schließen-nach-5-Min) - Quiet-Hours-Resolver (Send/Queue/Hold-Entscheidung pro Kanal; Urgent-Override; Queue-bis-Ruhezeit-Ende) — komplett neu
- Priority-Pipeline (urgent nie gebündelt; priority_threshold pro Preference) — komplett neu
- Pro-User-Push-Limit 10/h (Anti-Storm, M117) — neu
- Email-Bounce-Pause-Logik (7-Tage-Pause + Klartext-Hinweis im Center) — neu
- Override-Hierarchie (Channel-Stumm > Ruhezeit > User-Default > Channel-Default, §22.3.6) — neu
- Default-Preference-Sets pro Rolle als deklarative Templates (Vertriebler/PL/Monteur/Büro/GF/Marketing, LP-3) + Onboarding-Seeding — neu
- Event-Typen-Katalog (Enum/Registry über alle Producer-Module, Block I) — neu, wächst modulweise
- Anomalie-/Rate-Limit-Detection für Notification-Storms (AV #16) — neu
UI (neu / stark erweitern):
- M115: Reiches Notification-Center (Tabs Alle/Ungelesen/Markiert, Filter Event-Typ/Actor/Zeitraum/Priority, Suche, Bulk-Auswahl + Bulk-mark-read/clear mit >50-Bestätigung, Bundle-Expand „Alle anzeigen", Deeplink-Klick→mark-read, Actor-Avatar, Priority-Marker, Unread-Badge in Glocke, Tab-Title-Count). BP-Center ist Diagnostik-Liste — Umbau nötig.
- M116: Präferenz-Editor nach Modul gruppiert (3 Toggles + Priority-Threshold pro Event-Typ, Master „Alle Email aus", Reset-auf-Default, Default-Set-anwenden, Search). Quiet-Hours-Editor (Wochentag-Picker, Time-Picker, Urgent-Override, Master-Toggle, Konflikt-Hinweis). Geräte-Liste (Test-Push, Umbenennen, Revoke, Bulk-Revoke, Status-Marker, Empty-State App-Store-Link). Alle drei UI-Flächen fehlen (BP hat nur den minimalen Channel/Category-Toggle).
/admin/notifications/templatesread-only Email-Template-Anzeige +/admin/notifications/testTest-Notification-Werkzeug (Permissionnotifications.admin). BP-admin/notifications/*ist ein anderes Konzept (Novu-Operator-CRUD: channels/messages/subscribers) — als Inspiration, nicht direkt.
Querschnitt-Wiring (HiveGrid-spezifisch, neu):
- Sidebar-Eintrag in
packages/ui-hivegrid/src/app-shell/sidebar.config.ts(Gruppe gemäß INTEGRATION-CONTRACTS §1: „Notification-Center (Glocke) → M115-M117") — die BP-Sidebar/sidebar.config.tsexistiert in HiveGrid noch nicht (neue Struktur lt. AGENTS.md) @hivegrid/notificationsnotify()-Fassade gemäß INTEGRATION-CONTRACTS §7 (mitpriority,category,template,payload) — BP-notify()ist die Vorlage, aber die §7-Signatur (recipientId/template/priority) weicht ab- View-Model-Layer in
packages/view-models/**(pure) + Adapterpackages/ui-hivegrid/src/adapters/für die Subframe/BP-Components (AGENTS.md A-Disziplin) - Permission
notifications.admin+ Self-Scope-404-statt-403 (Spec Block K) - Feature-Flag (M006) + Smoke/Visual/Hot-Swap-Tests (M016/M017) + i18n-Keys (M003)
- Sicherheits-Versand-Failures → Modul-21-Audit (AV #6)
EU-Stack / Provider-Setup (Operator):
- Novu Self-Hosted auf Scaleway (BP nutzt
api.novu.coCloud — für LP-13 auf EU-Self-Hosted umstellen) - EU-SMTP-Provider-Config (MailerSend EU / Resend EU / Scaleway) — BP-MailerSend ist Region-konfigurierbar
- APNs/FCM-Credentials (
FCM_SERVICE_ACCOUNT_JSON,APNS_KEY/KEY_ID/TEAM_ID/BUNDLE_ID) — Worker-Boot liest sie bereits
Empfohlenes Vorgehen
Phase 0 — Datenmodell-Fundament (vor M115):
DeviceTokenquasi 1:1 übernehmen, nurdevice_label+os_versionergänzen.NotificationPreferenceChannelumPUSHerweitern;UserNotificationPreferenceumpriority_thresholdergänzen.- Neues reiches
Notification-Aggregat +NotificationBundle+NotificationQuietHoursals Forward-Only-Migration anlegen (Inverse-SQL als Kommentar, AGENTS.md F.28). Entscheiden:Notification-Aggregat zusätzlich zu/ersetzendNotificationDelivery— Empfehlung: reichesNotificationals SSOT,delivery_statusals JSONB pro Kanal (Spec Block A),NotificationDelivery-Diagnostik-Pattern für den Versand-Status weiterverwenden.
Phase 1 — M115 (In-App-Center):
4. @toolctrl/db/src/notifications.server.ts Lese-Pfade (getNotificationStreamSnapshot, markInAppNotificationDeliveryRead, Preferences) übernehmen, Schreib-Pfad aufs neue Modell anpassen.
5. SSE-Stack (packages/realtime + notifications-routes.ts GET /v1/notifications/stream) 1:1 übernehmen; Live-notification.update-Fan-out über shared-store/Redis-Pub/Sub ergänzen.
6. NotificationCenter.tsx + app.$tenant/notifications.tsx als Vorlage; aufs reiche M22-Center (Tabs/Filter/Suche/Bulk/Bundle/Actor/Priority) umbauen; via Adapter aus packages/ui-hivegrid einbinden (kein Direkt-Import, kein .server-Re-Export).
7. Glocke (NotificationsButton) übernehmen + Unread-Count-Badge + Tab-Title-Count ergänzen. Sidebar-Eintrag in sidebar.config.ts (§1).
Phase 2 — M116 (Settings + Quiet-Hours + Devices):
8. device-tokens.server.ts + devices-routes.ts (auf Self-Scope-Auth umstellen) + Push-Fan-out-Worker übernehmen; Geräte-Verwaltungs-UI neu bauen (Test-Push/Umbenennen/Revoke/Bulk).
9. Präferenz-Editor (Modul-gruppiert, 3 Kanäle + Priority-Threshold + Master-Toggle + Reset) — settings/notifications.tsx nur als Action/Loader-Muster.
10. Quiet-Hours-Modell + Resolver + Editor komplett neu; Default-Set-Seeding pro Rolle (deklarative Templates).
Phase 3 — M117 (Bündelung + Pipeline + Anti-Spam):
11. BullMQ-notifications-Queue + Outbox-Routing + Retry 1:1 nutzen.
12. Bundle-Manager mit Sliding-Window (5 Min, Counter, 100-Max, Close-after-idle) neu — foundation.bundleNotifications nur als Daten-Shape-Vorlage.
13. Pro-User-Push-Limit (10/h), Urgent-Bypass, Bounce-7-Tage-Pause, Override-Hierarchie neu.
14. Push-Sender (Novu EU) + Email-Sender (@toolctrl/mail + email-templates, Tenant-Branding via Modul 31) + In-App-Sender (write + SSE-update) verdrahten; delivery_status zurückschreiben.
15. Sicherheits-Failures → Modul-21-Audit; Anomalie-Detection (AV #16).
Querschnitt durchgehend: @hivegrid/notifications-notify()-Fassade als einziger Producer-Einstieg (§7), pure View-Models, i18n-Keys, Feature-Flag, Smoke/Visual/Hot-Swap-Tests, 404-statt-403, Klartext-Empty/Loading/Error-States (AGENTS.md A-F).