Files
HiveGrid-Wireframes/hivegrid-cloud/docs/boilerplate-mapping/M22-notifications.md
T

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, Pflichtenheft block5.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: kein title, 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: kein priority_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 nur device_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 granularere delivery_status pro Kanal als JSONB — z.B. bounced, skipped:user_preference, failed:smtp_timeout)
  • NotificationPreferenceChannel: IN_APP | EMAIL — PUSH fehlt (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_status JSONB pro Kanal (Spec Block A)
  • NotificationBundle-Modell (event_type_group, count, subject_summary, bundle_window_seconds, closed_at) — kein Pendant in BP
  • NotificationQuietHours-Modell (weekday, start/end, urgent_override, enabled) — kein Pendant in BP
  • PUSH als dritter NotificationPreferenceChannel-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 bundleNotifications gruppiert 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/templates read-only Email-Template-Anzeige + /admin/notifications/test Test-Notification-Werkzeug (Permission notifications.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.ts existiert in HiveGrid noch nicht (neue Struktur lt. AGENTS.md)
  • @hivegrid/notifications notify()-Fassade gemäß INTEGRATION-CONTRACTS §7 (mit priority, 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) + Adapter packages/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.co Cloud — 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):

  1. DeviceToken quasi 1:1 übernehmen, nur device_label + os_version ergänzen.
  2. NotificationPreferenceChannel um PUSH erweitern; UserNotificationPreference um priority_threshold ergänzen.
  3. Neues reiches Notification-Aggregat + NotificationBundle + NotificationQuietHours als Forward-Only-Migration anlegen (Inverse-SQL als Kommentar, AGENTS.md F.28). Entscheiden: Notification-Aggregat zusätzlich zu/ersetzend NotificationDelivery — Empfehlung: reiches Notification als SSOT, delivery_status als 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).