--- status: Accepted date: 2026-07-10 applyTo: - "Fuchs/Notifications/DraftPreviewHub.cs" - "Fuchs/Notifications/IDraftNotifier.cs" - "Fuchs/Notifications/DraftNotifier.cs" - "Fuchs/Program.cs" - "Fuchs/js/intranet/**" supersededBy: "" --- # 0007 — Draft-editing signals are targeted via a dedicated hub with per-draft groups ## Context Backend-authoritative draft editing (ADR 0006) needs to notify **exactly the one browser** editing a given draft that its cached state changed, is about to expire, or was closed. The existing `NotificationHub` (ADR 0002) deliberately **broadcasts** every business toast to all logged-in sessions and explicitly deferred per-user/targeted delivery as "a new decision". Draft coordination pings are high-frequency, per-editor, and must not spray to every session. ## Decision Draft signals use a **dedicated** SignalR hub, `DraftPreviewHub`, mapped at `/draftpreview` (separate from `NotificationHub` at `/notifications`). Targeting is by **SignalR group named after the draft's session token**: - The client calls the hub methods `JoinDraft(token)` / `LeaveDraft(token)` to subscribe/unsubscribe its connection to a draft's group. The hub carries **no commands** — only group membership (edits are POSTs; see ADR 0006). - The server sends via `IDraftNotifier` (`DraftNotifier`) to `Clients.Group(token)`: `draftReady{token,version}` (re-fetch), `draftExpiring{token,secondsLeft}` (idle warning), `draftClosed{token,reason}` (session evicted/discarded → close the editor). - Like `EventService`, delivery failures are logged and swallowed — a missed coordination ping must never fail the underlying operation; the client also re-syncs on reconnect and on its next POST. Business success/failure messages for draft operations (e.g. "Zwischenstand gespeichert") continue to flow through `IEventService`/`NotificationHub`, **not** this hub — the two channels stay separate. ## Consequences - The session **token doubles as the group name**; it is an opaque GUID and must not encode sensitive data. Any browser that knows a token can join its group, so tokens must be treated as capabilities and only handed to the authenticated editor that opened the draft. - Adding a new draft signal means adding a method to `IDraftNotifier` + `DraftNotifier` and a client handler in `$fis.draft` — not overloading the business notification path. - ADR 0002 is unchanged: `NotificationHub` stays broadcast-only for toasts. This hub is the answer to its "if per-user targeting becomes necessary, that is a new decision". - Multi-instance scale-out needs a SignalR backplane for group delivery — same limitation as ADR 0006. ## Alternatives considered - **Reuse `NotificationHub` with groups**: rejected — it would entangle broadcast toasts with targeted, high-frequency editing pings and force ADR 0002's broadcast contract to change. A separate hub keeps the concerns and their decisions independent. - **Per-user groups (by account id)**: rejected — a user may open two drafts/tabs; per-draft-token groups target the precise editor and naturally support that.