Zum Inhalt springen

Benachrichtigungen

In-App-Benachrichtigungen über das persistente Notification-Modell (Glocke im Header). Bewusst keine Browser-/Push-Benachrichtigungen.

Wenn ein Beitrag in einer aktiven Veranstaltung oder Veranstaltungsreihe erstellt wird, erhalten alle anderen bestätigten Teilnehmenden dieses Kontexts eine Benachrichtigung. Der Autor selbst wird ausgeschlossen. Bei einem Termin innerhalb einer Reihe werden direkte Termin- und Reihen-Zuordnungen zusammengeführt und doppelte Empfänger entfernt. Zusätzlich erhält die hinterlegte Kontaktperson (contactAccountId) des Events/der Reihe bei einem neuen Beitrag (NEW_POST) ebenfalls eine Meldung, auch wenn sie selbst keine bestätigte Teilnahme hat — bewusst nur bei neuen Beiträgen, nicht bei jeder einzelnen Antwort, um Vielschreiber-Threads nicht zu einer Benachrichtigungsflut für die verantwortliche Person zu machen (TASK-303).

Für Beiträge/Antworten direkt auf einer Reihe (nicht auf einem einzelnen Termin) gilt zusätzlich eine Aktivitätsregel (Entscheidung 2026-07-30, TASK-306): NEW_POST und NEW_REPLY verteilen nur, wenn die Reihe aktuell EventStatus.ACTIVE ist — analog zur bereits bestehenden Regel für einzelne Termine. Admin-/Management-Accounts können zwar auch in Entwurfs- oder archivierten Reihen posten (Zugriffsprüfung kennt keine Statusgrenze), das löst dort aber bewusst keine Meldung an bestätigte Teilnehmende aus. Statuswechsel-Meldungen (EVENT_UPDATED, z. B. „Reihe archiviert”) sind davon ausdrücklich ausgenommen — sie kündigen genau den Wechsel in/aus ACTIVE an und würden sich sonst selbst unterdrücken.

Benachrichtigungen zu neuen Beiträgen und Reaktionen führen direkt zum betroffenen Beitrag, Benachrichtigungen zu Antworten direkt zur jeweiligen Antwort. Liegt der Inhalt außerhalb der zuerst geladenen Beiträge, wird er gezielt nachgeladen. Wurde der Inhalt inzwischen entfernt, bleibt der Bereich „Beiträge“ der Veranstaltung als Rückfallziel sichtbar.

Verbindliche Produktregel für alle elf NotificationType-Werte (TASK-303):

TypAuslöserEmpfängerSelbstausschlussLinkkontext
NEW_POSTNeuer BeitragBestätigte Teilnehmende des Kontexts + KontaktpersonAutor:inEvent/Reihe + Beitrag
NEW_REPLYNeue AntwortBestätigte Teilnehmende des Kontexts (bewusst alle, nicht nur Beteiligte — Entscheidung 2026-07-30)Autor:inEvent/Reihe + Antwort
REACTIONReaktion gesetzt, geändert oder entferntAutor:in des Beitrags (einzelne Person)Reagierende Person = Autor:inEvent/Reihe + Beitrag
DOCUMENTDokument hochgeladenBestätigte Teilnehmende des KontextsHochladende Admin/Management-PersonEvent/Reihe + #dokumente
RECORDINGAufzeichnung hochgeladenBestätigte Teilnehmende des KontextsHochladende Admin/Management-PersonEvent/Reihe + #aufzeichnungen
EVENT_UPDATEDTermin-, Titel- oder Statusänderung, Löschung eines Events, Entfernen eines Termins aus einer ReiheBestätigte Teilnehmende des KontextsBearbeitende Admin/Management-PersonEvent/Reihe
FEEDBACKFeedback-Link nach Ende von Event/Reihe verfügbarDie betroffene Person selbst (systemgeneriert, kein Auslöser-Account)Event/Reihe
PARTICIPANT_ADDEDZuordnung zu Event/Reihe angelegtDie zugeordnete Person (einzelne Person)— (Ziel ist immer die betroffene Person)Event/Reihe
PARTICIPANT_REMOVEDZuordnung zu Event/Reihe entferntDie entfernte Person (einzelne Person)Immer Übersicht (/), siehe unten
RSVP_UPDATEDZu-/Absage einer TeilnahmeAlle ADMIN- und MANAGEMENT-AccountsNicht nötig — Auslöser ist immer Role.PARTICIPANTEvent/Reihe
ACCOUNT_DELETEDSelbstlöschung des eigenen KontosAlle ADMIN-Accounts (bewusst nicht Management)Nicht nötig — betroffener Account wird geleertÜbersicht (/)

PARTICIPANT_ADDED/PARTICIPANT_REMOVED und ACCOUNT_DELETED haben keinen klassischen „Selbstausschluss”, weil der Empfängerkreis dort ohnehin nicht mit dem Auslöser überschneidet (Zielperson bzw. Admin-Rundruf statt Teilnehmenden-Liste).

Reaktionsmeldungen bilden den aktuellen Zustand ab

Abschnitt betitelt „Reaktionsmeldungen bilden den aktuellen Zustand ab“

REACTION-Benachrichtigungen sind kein historisches Aktivitäts-Log, sondern bilden den aktuellen Reaktionszustand ab (Entscheidung 2026-07-30, TASK-304): Pro Beitrag und reagierender Person existiert höchstens eine Meldung.

  • Reagieren (erstmalig oder mit geändertem Emoji): Die bestehende Meldung wird per Upsert aktualisiert (neues Emoji, wieder ungelesen gesetzt) statt eine weitere anzulegen. Ein Unique-Index auf (accountId, dedupeKey) mit dedupeKey = "reaction:<postId>:<reactorId>" macht das auch bei gleichzeitigen Änderungen atomar — verhindert genau das Duplikat-Problem, das vor dieser Entscheidung live beobachtet wurde (mehrfache Meldungen für dieselbe Reaktion durch schnell wiederholtes Reagieren).
  • Reaktion entfernen: Die zugehörige Meldung wird gelöscht, nicht nur unverändert belassen — keine Reaktion mehr bedeutet keine Meldung mehr.

dedupeKey ist derselbe Mechanismus, der bereits FEEDBACK-Meldungen dedupliziert (TASK-302); das Feld hieß zunächst feedbackContext und wurde zu diesem Zweck auf dedupeKey verallgemeinert.

EVENT_UPDATED deckt folgende Anlässe ab (Entscheidung 2026-07-30, TASK-305):

  • Feldänderungen an einem Event: Datum/Uhrzeit, Format, Ort, Online-Link, Titel und Status. Alle Änderungen aus einem Speichervorgang werden zu einer Meldung gebündelt (buildEventChanges sammelt die betroffenen Felder, erst danach wird eine einzelne Notification erzeugt) — kein Aufsplitten in mehrere Meldungen pro Speichervorgang.
  • Löschung eines Events (deleteEvent): Empfänger werden vor dem Löschen erfasst (direkte Termin-Teilnahme + Reihen-Teilnahme, falls das Event Teil einer Reihe war), die Meldung selbst wird erst danach ohne eventId verschickt — der Datensatz existiert zu dem Zeitpunkt nicht mehr und würde über onDelete: Cascade sonst die gerade erst angelegte Notification gleich wieder mitlöschen. War das Event Teil einer Reihe, bleibt die Reihen-seriesId als Linkziel erhalten.
  • Entfernen eines Termins aus einer Reihe (removeEventFromSeries): Der Termin selbst bleibt bestehen (wird nur eigenständig), Empfänger sind sowohl die direkten Termin-Teilnehmenden als auch die Reihen-Teilnehmenden, da ein Reihentermin überwiegend über die Reihen-Zuordnung wahrgenommen wird, nicht über eine separate Termin-Zuordnung.

Bewusst nicht benachrichtigungswürdig: rein redaktionelle Änderungen wie Beschreibungstext oder Thumbnail — diese lösen keine EVENT_UPDATED-Meldung aus, um die Glocke nicht mit für Teilnehmende operativ irrelevanten Hinweisen zu fluten.

Änderungsbeschreibungen sind sprachneutral gespeichert

Abschnitt betitelt „Änderungsbeschreibungen sind sprachneutral gespeichert“

EVENT_UPDATED-Meldungen (Feldänderungen, Reihen-Statuswechsel, neuer Termin) speichern seit der Entscheidung 2026-07-30 (TASK-307) keine vorformulierten deutschen Texte mehr in paramsJson, sondern stabile Schlüssel und Rohwerte:

  • Feldänderungen: buildEventChanges liefert strukturierte Deskriptoren ({ key: "title" }, { key: "format", from, to }, …) statt fertiger Sätze. Gespeichert wird changeKeys (kommagetrennte Schlüssel) plus formatFrom/formatTo bzw. statusFrom/statusTo als rohe Enum-Werte.
  • Reihen-Statuswechsel: statusFrom/statusTo statt vorformatierter beforeLabel/afterLabel.
  • Neuer Termin: dateIso (ISO-String) statt eines mit de-DE fest formatierten Datumstexts.

Übersetzt wird erst beim Rendern in components/notification-bell.tsx, mit der aktiven Locale der lesenden Person — die ist zum Anlegezeitpunkt der Meldung nicht bekannt, locale liegt nur als Cookie der aktuellen Anfrage vor, nicht als Feld am Account. Format- und Statuswerte nutzen dafür die bereits bestehenden lokalisierten Maps t.eventFormat/t.eventStatus.

Bereits vor dieser Änderung gespeicherte Meldungen enthalten die neuen Rohwerte nicht und behalten dadurch unverändert ihren damals auf Deutsch vorformulierten Text als Rückfall (kein Blindtext, keine kaputte Anzeige, aber weiterhin einsprachig Deutsch für historische Einträge).

Server Actions warten die persistente Anlage ausgelöster Benachrichtigungen ab, bevor sie antworten oder weiterleiten. Die Zustellung läuft dabei bewusst innerhalb der lokal gehosteten Anwendung und benötigt weder eine externe Queue noch einen Cloud-Dienst.

Ein reiner Benachrichtigungsfehler macht eine bereits erfolgreich gespeicherte Fachaktion nicht rückgängig. Er wird stattdessen mit Benachrichtigungstyp, Auslöser, handelndem Account und betroffenem Event-, Reihen- oder Inhaltskontext strukturiert protokolliert. Das gilt einheitlich für Beiträge, Antworten, Reaktionen, Teilnahmeentscheidungen, Zuordnungen, Importe, Inhaltsuploads und administrative Änderungen.

Der Link einer Benachrichtigung wird zentral aufgeloest (getNotificationHref in lib/notification-links.ts) und fragt dafuer den aktuellen Datenbankstand ab, nicht den Stand zum Anlegezeitpunkt der Meldung:

  • Gehoert der betroffene Termin inzwischen zu einer Reihe, fuehrt der Link zur Reihenseite mit vorselektiertem Termin (/series/<id>?event=<id>) statt zu /event/<id> — Reihentermine sind dort bewusst nicht direkt erreichbar (notFound()) und nur ueber die Reihenseite zugaenglich.
  • Meldungen zu Dokumenten und Aufzeichnungen springen zusaetzlich zum passenden Inhaltsbereich (#dokumente / #aufzeichnungen).
  • PARTICIPANT_REMOVED fuehrt immer zur Uebersicht (/), da der Zugriff auf den ehemaligen Kontext zu diesem Zeitpunkt bereits entzogen ist.

Initiale und nachgeladene Meldungen (/api/notifications/more) nutzen dieselbe Funktion.

Wenn Teilnehmende einer Veranstaltung oder Reihe zu- oder absagen (RSVP_UPDATED), erhalten alle Accounts mit Rolle ADMIN und MANAGEMENT eine Meldung — nicht nur Admins. Management-Accounts dürfen Events und Reihen über requireManagementOrAdmin genauso verwalten wie Admins, ohne diese Meldung würden sie Zu-/Absagen für die von ihnen betreuten Veranstaltungen verpassen. Die hinterlegte Kontaktperson eines Events/einer Reihe ist bei der Auswahl im Formular ohnehin auf ADMIN/MANAGEMENT beschränkt und damit über diese Rollenkombination bereits erreicht — eine separate Sonderregel für die Kontaktperson ist daher nicht nötig.