# SignalRSyncBroadcaster

- File ID: file:10i5sd6:signalrsyncbroadcaster-cs
- Path: NexusReader.Infrastructure/RealTime/SignalRSyncBroadcaster.cs
- Role: service
- Layer: infrastructure

## Purpose

Provides a SignalR-based implementation of ISyncBroadcaster to push sync and ingestion progress updates to a user's connected devices via IHubContext<SyncHub>.

## Key Details

The BroadcastProgressAsync method contains a branch that checks excludedConnectionId, but both branches call Clients.User(userId).SendAsync — comments note SignalR HubContext limitations around excluding a specific connection when using .User(id). All sends pass a CancellationToken and target the user's connected clients.

## Symbols

- SignalRSyncBroadcaster: Provides a SignalR-based implementation of ISyncBroadcaster to push sync and ingestion progress updates to a user's connected devices via IHubContext<SyncHub>.
- SignalRSyncBroadcaster(): Constructs the broadcaster with the SignalR hub context.
- BroadcastProgressAsync(): Sends a 'ProgressUpdated' notification to all connected devices of a user (attempts to consider an excluded connection id but falls back to User-scoped broadcast).
- BroadcastIngestionProgressAsync(): Pushes ingestion status messages and numeric progress (0.0-1.0) to a user's active sessions via SignalR.
- SignalRSyncBroadcaster: SignalR-based implementation of ISyncBroadcaster that pushes sync and ingestion progress to a user's connected devices.

## Dependencies

- No resolved dependencies.

## Calls

- No resolved calls.

## Unresolved References

- dependency-target: NexusReader.Application.Abstractions.Messaging (ISyncBroadcaster)
- dependency-target: NexusReader.Infrastructure.RealTime.SyncHub (referenced hub type)
- dependency-target: Microsoft.AspNetCore.SignalR (IHubContext, Clients.User, SendAsync)
- call-target: unknown
- call-target: unknown
- call-target: unknown
- interaction-target: .SendAsync("ProgressUpdated", pageId, timestamp, cancellationToken: cancellationToken);
- interaction-target: Await _hubContext.Clients.User(userId).SendAsync('ProgressUpdated', pageId, timestamp, cancellationToken: cancellationToken)
- interaction-target: .SendAsync("ProgressUpdated", pageId, timestamp, cancellationToken: cancellationToken);
- interaction-target: Await _hubContext.Clients.User(userId).SendAsync('ProgressUpdated', pageId, timestamp, cancellationToken: cancellationToken)
- interaction-target: .SendAsync("IngestionProgress", message, progress, cancellationToken: cancellationToken);
- interaction-target: Await _hubContext.Clients.User(userId).SendAsync('IngestionProgress', message, progress, cancellationToken: cancellationToken)

## Coverage

- Symbols: 5
- Executable symbols: 3
- Executable symbols with control flow: 3
- Grounded edges: 5
- Unresolved references: 12
- Control-flow fragments: 3
- Unresolved jumps: 3
- Unsupported constructs: 0
- Control-flow coverage: 100%
- Required diagrams: 2
- Generated diagrams: 4

## Control Flow Fragments

- SignalRSyncBroadcaster: 3 nodes, 2 edges, 0 jumps, 0 unsupported constructs
- BroadcastProgressAsync: 23 nodes, 25 edges, 2 jumps, 0 unsupported constructs
- BroadcastIngestionProgressAsync: 7 nodes, 6 edges, 1 jumps, 0 unsupported constructs

## Diagrams

### SignalRSyncBroadcaster Neighborhood

```mermaid
flowchart LR
    class_7rymm7_signalrsyncbroadcaster("SignalRSyncBroadcaster")
    file_10i5sd6_signalrsyncbroadcaster_cs("SignalRSyncBroadcaster")
    method_115yp5z_broadcastprogressasync[["BroadcastProgressAsync()"]]
    method_1mtzd71_signalrsyncbroadcaster[["SignalRSyncBroadcaster()"]]
    method_1s8oq59_broadcastingestionprogressasync[["BroadcastIngestionProgressAsync()"]]
    type_1lq0yf5_signalrsyncbroadcaster[/"SignalRSyncBroadcaster"/]
```

### SignalRSyncBroadcaster Execution Overview

```mermaid
flowchart TD
    class_7rymm7_signalrsyncbroadcaster("SignalRSyncBroadcaster")
    method_115yp5z_broadcastprogressasync[["BroadcastProgressAsync()"]]
    method_1mtzd71_signalrsyncbroadcaster[["SignalRSyncBroadcaster()"]]
    method_1s8oq59_broadcastingestionprogressasync[["BroadcastIngestionProgressAsync()"]]
    unresolved_unresolved_1cd76nj_call_target{"Unresolved: IHubContext<SyncHub>.Clients.User(...).SendAsync"}
    unresolved_unresolved_1smgfwa_call_target{"Unresolved: IHubContext<SyncHub>.Clients.User(...).SendAsync"}
    unresolved_unresolved_1t9lbc2_interaction_target{"Unresolved: .SendAsync(\\"IngestionProgress\\", message, progress, cancellationToken: cancellationToken);"}
    unresolved_unresolved_7ilg33_interaction_target{"Unresolved: Await _hubContext.Clients.User(userId).SendAsync('ProgressUpdated', pageId, timestamp, cancellationToken: cancellationToken)"}
    unresolved_unresolved_kss05k_interaction_target{"Unresolved: Await _hubContext.Clients.User(userId).SendAsync('IngestionProgress', message, progress, cancellationToken: cancellationToken)"}
    unresolved_unresolved_yb10mn_interaction_target{"Unresolved: .SendAsync(\\"ProgressUpdated\\", pageId, timestamp, cancellationToken: cancellationToken);"}
    class_7rymm7_signalrsyncbroadcaster -->|contains| method_115yp5z_broadcastprogressasync
    class_7rymm7_signalrsyncbroadcaster -->|contains| method_1mtzd71_signalrsyncbroadcaster
    class_7rymm7_signalrsyncbroadcaster -->|contains| method_1s8oq59_broadcastingestionprogressasync
    class_7rymm7_signalrsyncbroadcaster -->|unresolved interaction| unresolved_unresolved_1t9lbc2_interaction_target
    class_7rymm7_signalrsyncbroadcaster -->|unresolved interaction| unresolved_unresolved_7ilg33_interaction_target
    class_7rymm7_signalrsyncbroadcaster -->|unresolved interaction| unresolved_unresolved_7ilg33_interaction_target
    class_7rymm7_signalrsyncbroadcaster -->|unresolved interaction| unresolved_unresolved_kss05k_interaction_target
    class_7rymm7_signalrsyncbroadcaster -->|unresolved interaction| unresolved_unresolved_yb10mn_interaction_target
    class_7rymm7_signalrsyncbroadcaster -->|unresolved interaction| unresolved_unresolved_yb10mn_interaction_target
    method_115yp5z_broadcastprogressasync -->|unresolved call| unresolved_unresolved_1smgfwa_call_target
    method_115yp5z_broadcastprogressasync -->|unresolved call| unresolved_unresolved_1smgfwa_call_target
    method_1s8oq59_broadcastingestionprogressasync -->|unresolved call| unresolved_unresolved_1cd76nj_call_target
```

### BroadcastProgressAsync() Control Flow

_Warnings: diagram method_115yp5z_broadcastprogressasync__fragment_diagram exceeds preferred complexity thresholds (23 nodes, 25 edges)._

```mermaid
---
config:
  flowchart:
    defaultRenderer: "elk"
---
flowchart TD
    method_115yp5z_broadcastprogressasync__call_34_IHubContext_SyncHub_Clients_User_SendAsync[["IHubContext<SyncHub>.Clients.User(...).SendAsync"]]
    method_115yp5z_broadcastprogressasync__call_44_IHubContext_SyncHub_Clients_User_SendAsync[["IHubContext<SyncHub>.Clients.User(...).SendAsync"]]
    method_115yp5z_broadcastprogressasync__action_30_broadcastprogressasync_branch_30_0__condition{"!string.IsNullOrEmpty(excludedConnectionId)"}
    method_115yp5z_broadcastprogressasync__action_30_broadcastprogressasync_branch_30_0__false("false")
    method_115yp5z_broadcastprogressasync__action_30_broadcastprogressasync_branch_30_0__merge("merge")
    method_115yp5z_broadcastprogressasync__action_30_broadcastprogressasync_branch_30_0__true("true")
    method_115yp5z_broadcastprogressasync__action_30_guard_clause_30__condition{"!string.IsNullOrEmpty(excludedConnectionId)"}
    method_115yp5z_broadcastprogressasync__action_30_guard_clause_30__false("else: normal send")
    method_115yp5z_broadcastprogressasync__action_30_guard_clause_30__merge("merge")
    method_115yp5z_broadcastprogressasync__action_30_guard_clause_30__true("enter branch: attempts exclusion comment")
    method_115yp5z_broadcastprogressasync__action_32_broadcastprogressasync_await_32_1__await[["Waits for async work"]]
    method_115yp5z_broadcastprogressasync__action_36_comment_36__linear("note about exclusion limitations")
    method_115yp5z_broadcastprogressasync__action_40_broadcastprogressasync_fallback_40_3__condition{"Falls back to alternate path"}
    method_115yp5z_broadcastprogressasync__action_40_broadcastprogressasync_fallback_40_3__false("otherwise")
    method_115yp5z_broadcastprogressasync__action_40_broadcastprogressasync_fallback_40_3__merge("merge")
    method_115yp5z_broadcastprogressasync__action_40_broadcastprogressasync_fallback_40_3__true("fallback")
    method_115yp5z_broadcastprogressasync__action_42_broadcastprogressasync_await_42_4__await[["Waits for async work"]]
    method_115yp5z_broadcastprogressasync__entry(["BroadcastProgressAsync entry"])
    method_115yp5z_broadcastprogressasync__action_34_broadcastprogressasync_external_call_34_2__linear("Calls external dependency")
    method_115yp5z_broadcastprogressasync__action_34_external_call_34__linear("send progress update to user")
    method_115yp5z_broadcastprogressasync__action_44_broadcastprogressasync_external_call_44_5__linear("Calls external dependency")
    method_115yp5z_broadcastprogressasync__action_44_external_call_44__linear("send progress update to user (else branch)")
    method_115yp5z_broadcastprogressasync__implicit_return(["return"])
    method_115yp5z_broadcastprogressasync__action_30_broadcastprogressasync_branch_30_0__condition -->|false| method_115yp5z_broadcastprogressasync__action_30_broadcastprogressasync_branch_30_0__false
    method_115yp5z_broadcastprogressasync__action_30_broadcastprogressasync_branch_30_0__condition -->|true| method_115yp5z_broadcastprogressasync__action_30_broadcastprogressasync_branch_30_0__true
    method_115yp5z_broadcastprogressasync__action_30_broadcastprogressasync_branch_30_0__false -->|next| method_115yp5z_broadcastprogressasync__action_30_broadcastprogressasync_branch_30_0__merge
    method_115yp5z_broadcastprogressasync__action_30_broadcastprogressasync_branch_30_0__merge -->|next| method_115yp5z_broadcastprogressasync__action_30_guard_clause_30__condition
    method_115yp5z_broadcastprogressasync__action_30_broadcastprogressasync_branch_30_0__true -->|next| method_115yp5z_broadcastprogressasync__action_30_broadcastprogressasync_branch_30_0__merge
    method_115yp5z_broadcastprogressasync__action_30_guard_clause_30__condition -->|else: normal send| method_115yp5z_broadcastprogressasync__action_30_guard_clause_30__false
    method_115yp5z_broadcastprogressasync__action_30_guard_clause_30__condition -->|enter branch: attempts exclusion comment| method_115yp5z_broadcastprogressasync__action_30_guard_clause_30__true
    method_115yp5z_broadcastprogressasync__action_30_guard_clause_30__false -->|next| method_115yp5z_broadcastprogressasync__action_30_guard_clause_30__merge
    method_115yp5z_broadcastprogressasync__action_30_guard_clause_30__merge -->|next| method_115yp5z_broadcastprogressasync__action_32_broadcastprogressasync_await_32_1__await
    method_115yp5z_broadcastprogressasync__action_30_guard_clause_30__true -->|next| method_115yp5z_broadcastprogressasync__action_30_guard_clause_30__merge
    method_115yp5z_broadcastprogressasync__action_32_broadcastprogressasync_await_32_1__await -->|next| method_115yp5z_broadcastprogressasync__action_34_external_call_34__linear
    method_115yp5z_broadcastprogressasync__action_34_broadcastprogressasync_external_call_34_2__linear -->|calls| method_115yp5z_broadcastprogressasync__call_34_IHubContext_SyncHub_Clients_User_SendAsync
    method_115yp5z_broadcastprogressasync__action_34_external_call_34__linear -->|next| method_115yp5z_broadcastprogressasync__action_34_broadcastprogressasync_external_call_34_2__linear
    method_115yp5z_broadcastprogressasync__action_36_comment_36__linear -->|fallback| method_115yp5z_broadcastprogressasync__action_40_broadcastprogressasync_fallback_40_3__condition
    method_115yp5z_broadcastprogressasync__action_40_broadcastprogressasync_fallback_40_3__condition -->|otherwise| method_115yp5z_broadcastprogressasync__action_40_broadcastprogressasync_fallback_40_3__false
    method_115yp5z_broadcastprogressasync__action_40_broadcastprogressasync_fallback_40_3__condition -->|fallback| method_115yp5z_broadcastprogressasync__action_40_broadcastprogressasync_fallback_40_3__true
    method_115yp5z_broadcastprogressasync__action_40_broadcastprogressasync_fallback_40_3__false -->|next| method_115yp5z_broadcastprogressasync__action_40_broadcastprogressasync_fallback_40_3__merge
    method_115yp5z_broadcastprogressasync__action_40_broadcastprogressasync_fallback_40_3__merge -->|next| method_115yp5z_broadcastprogressasync__action_42_broadcastprogressasync_await_42_4__await
    method_115yp5z_broadcastprogressasync__action_40_broadcastprogressasync_fallback_40_3__true -->|next| method_115yp5z_broadcastprogressasync__action_40_broadcastprogressasync_fallback_40_3__merge
    method_115yp5z_broadcastprogressasync__action_42_broadcastprogressasync_await_42_4__await -->|next| method_115yp5z_broadcastprogressasync__action_44_external_call_44__linear
    method_115yp5z_broadcastprogressasync__action_44_broadcastprogressasync_external_call_44_5__linear -->|calls| method_115yp5z_broadcastprogressasync__call_44_IHubContext_SyncHub_Clients_User_SendAsync
    method_115yp5z_broadcastprogressasync__action_44_external_call_44__linear -->|next| method_115yp5z_broadcastprogressasync__action_44_broadcastprogressasync_external_call_44_5__linear
    method_115yp5z_broadcastprogressasync__call_34_IHubContext_SyncHub_Clients_User_SendAsync -->|next| method_115yp5z_broadcastprogressasync__action_36_comment_36__linear
    method_115yp5z_broadcastprogressasync__call_44_IHubContext_SyncHub_Clients_User_SendAsync -->|return| method_115yp5z_broadcastprogressasync__implicit_return
    method_115yp5z_broadcastprogressasync__entry -->|next| method_115yp5z_broadcastprogressasync__action_30_broadcastprogressasync_branch_30_0__condition
```

### BroadcastIngestionProgressAsync() Control Flow

```mermaid
flowchart TD
    method_1s8oq59_broadcastingestionprogressasync__call_59_IHubContext_SyncHub_Clients_User_SendAsync[["IHubContext<SyncHub>.Clients.User(...).SendAsync"]]
    method_1s8oq59_broadcastingestionprogressasync__action_55_comment_55__linear("intent comment")
    method_1s8oq59_broadcastingestionprogressasync__action_57_broadcastingestionprogressasync_await_57_0__await[["Waits for async work"]]
    method_1s8oq59_broadcastingestionprogressasync__entry(["BroadcastIngestionProgressAsync entry"])
    method_1s8oq59_broadcastingestionprogressasync__action_59_broadcastingestionprogressasync_external_call_59_1__linear("Calls external dependency")
    method_1s8oq59_broadcastingestionprogressasync__action_59_external_call_59__linear("send ingestion progress to user")
    method_1s8oq59_broadcastingestionprogressasync__implicit_return(["return"])
    method_1s8oq59_broadcastingestionprogressasync__action_55_comment_55__linear -->|next| method_1s8oq59_broadcastingestionprogressasync__action_57_broadcastingestionprogressasync_await_57_0__await
    method_1s8oq59_broadcastingestionprogressasync__action_57_broadcastingestionprogressasync_await_57_0__await -->|next| method_1s8oq59_broadcastingestionprogressasync__action_59_external_call_59__linear
    method_1s8oq59_broadcastingestionprogressasync__action_59_broadcastingestionprogressasync_external_call_59_1__linear -->|calls| method_1s8oq59_broadcastingestionprogressasync__call_59_IHubContext_SyncHub_Clients_User_SendAsync
    method_1s8oq59_broadcastingestionprogressasync__action_59_external_call_59__linear -->|next| method_1s8oq59_broadcastingestionprogressasync__action_59_broadcastingestionprogressasync_external_call_59_1__linear
    method_1s8oq59_broadcastingestionprogressasync__call_59_IHubContext_SyncHub_Clients_User_SendAsync -->|return| method_1s8oq59_broadcastingestionprogressasync__implicit_return
    method_1s8oq59_broadcastingestionprogressasync__entry -->|next| method_1s8oq59_broadcastingestionprogressasync__action_55_comment_55__linear
```

## Diagram Validation

- Status: warn
- Warning: Control-flow extraction left 3 unresolved jumps.
- Warning: control-flow: diagram method_115yp5z_broadcastprogressasync__fragment_diagram exceeds preferred complexity thresholds (23 nodes, 25 edges).
