--- status: Accepted date: 2026-07-03 applyTo: - "Fuchs/Notifications/**" - "Fuchs/js/intranet/**" - "Fuchs/wwwroot/web/**" - "Fuchs/Program.cs" supersededBy: "" --- # 0002 — Backend notifications reach the GUI via a SignalR push to every logged-in session ## Context Domain events (see [0001](0001-domain-events-and-notification-triggers.md)) need to reach whichever browser session(s) a user has open, in near real time, without the client polling. ## Decision - `NotificationHub` (`Fuchs/Notifications/NotificationHub.cs`) is an `[Authorize]` SignalR `Hub` mapped at `/notifications` in `Program.cs` (`app.MapHub("/notifications")`). - `EventService.PublishAsync` sends every `GuiNotification` to `_hub.Clients.All.SendAsync("notification", notification, ...)`. Delivery is currently broadcast to all connected (authenticated) clients, not targeted per-user — any logged-in session receives every notification. - Publish failures are caught and logged (`_logger.LogWarning`) rather than thrown — a notification-delivery failure must never fail the underlying business operation that triggered it. - On the client, `$fis.notifications` (`Fuchs/js/intranet/fis_main.js`) opens the SignalR connection once a logged-in `useraccount_id` is known, listens for the `"notification"` event, and calls `push(notification)` to render a dismissible toast into `#notification_frame`. The toast is styled by `notification.severity` (`"error"` vs `"info"`), giving failures a distinct highlighted appearance from successes. - `GuiNotification.Severity` is derived by `EventService.IsFailure` from the `DomainEventType` — failure event types render as `"error"`, everything else as `"info"`. ## Consequences - Any new `DomainEventType` that represents a failure must be added to `EventService.IsFailure` or it will render as a plain info toast instead of being visually flagged. - Because delivery is broadcast (not user-scoped), notifications are not a substitute for private/sensitive data — `Context`/`Message` content must stay appropriate for any logged-in user to see. If per-user targeting becomes necessary, that is a new decision (SignalR groups keyed by user ID), not a silent change to this one. - The hub requires authentication; unauthenticated sessions never connect and never receive notifications. - Frontend rendering logic lives in `fis_main.js`/`fis.js` — keep the built `wwwroot/web/fis.js`/`fis.min.js` in sync via the gulp build (see `CLAUDE.md` Build & Test) whenever the notification client code changes. ## Alternatives considered - **Per-user SignalR groups**: more correct long-term but adds group join/leave lifecycle management; deferred until a concrete need for private notifications arises. - **Server-Sent Events / long polling**: rejected — SignalR was already the chosen real-time transport and needs no extra infrastructure.