Real time

Everything the server tells a browser goes through one endpoint and one connection per tab:

GET /api/v1/events/stream?topic=kind:value

It is a server-sent events stream: plain HTTP, through the gateway, with the browser's EventSource reconnecting on its own.

A frame is a signal, never the data

When a notification is created, the server does not push the notification. It pushes a signal on the recipient's topic, and the client re-reads through the normal API.

That one rule removes a whole class of bugs: a dropped frame, a missed reconnect or a slow tab costs a stale second, never a wrong screen, because the data always comes from the same read endpoints as on a page load.

On the server

A domain publishes through a port, IServerEventPublisher, with a topic:

// Blouppy.Notifications — after a notification is stored.
ServerEventTopic.ForUser(notification.RecipientId)

Who may follow a topic is decided by an IServerEventTopicAuthorizer, one per kind of topic. The Starter ships one, UserServerEventTopicAuthorizer: a user may follow their own stream and nobody else's.

A new kind of push is a new authorizer — not a new route, and not a second connection.

The registry of open connections lives in each process. To reach a browser connected to another replica, publishing goes through the bus to a queue that every replica has its own copy of (ServerEventBroadcastConsumer, registered with AddInstanceScopedConsumer).

In the Angular app

core/real-time/ServerEventsService owns the single EventSource and hands out a stream per topic. NotificationService, for instance, re-reads the bell's unread count when a frame arrives on the user's topic.