From 5a52785454a831f02c0b63a679e7fa827d3825d1 Mon Sep 17 00:00:00 2001 From: iammukeshm Date: Tue, 9 Jun 2026 14:52:31 +0530 Subject: [PATCH] fix(sse): flush stream headers eagerly so clients connect instantly The SSE stream handler set the response headers and then entered the read loop without writing anything, so Kestrel buffered the headers until the first body write - which was the heartbeat up to 15s away. The browser's fetch() resolves on response headers, so the dashboard sat on "connecting" for up to 15s on every connect/reconnect with an idle stream. Write a no-op ":connected" SSE comment and flush immediately after Connect() so the headers go out at once and the client flips to "connected" right away. Co-Authored-By: Claude Opus 4.8 (1M context) --- src/BuildingBlocks/Web/Sse/SseEndpoints.cs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/BuildingBlocks/Web/Sse/SseEndpoints.cs b/src/BuildingBlocks/Web/Sse/SseEndpoints.cs index ab7ceb0455..78961205b9 100644 --- a/src/BuildingBlocks/Web/Sse/SseEndpoints.cs +++ b/src/BuildingBlocks/Web/Sse/SseEndpoints.cs @@ -61,6 +61,16 @@ public static IEndpointRouteBuilder MapHeroSseEndpoints(this IEndpointRouteBuild context.Response.Headers["X-Accel-Buffering"] = "no"; // disable nginx buffering var (connectionId, reader) = connectionManager.Connect(principal.UserId, principal.TenantId); + + // Flush the response headers + an initial comment immediately. Kestrel buffers + // response headers until the first body write, and our first write would otherwise + // be the heartbeat up to HeartbeatInterval (15s) away — so the client's fetch() + // promise (which resolves on response headers) would sit pending and the UI would + // show "connecting" for up to 15s on every connect/reconnect. Writing a no-op SSE + // comment now sends the headers and lets the client flip to "connected" at once. + await context.Response.WriteAsync(":connected\n\n", cancellationToken).ConfigureAwait(false); + await context.Response.Body.FlushAsync(cancellationToken).ConfigureAwait(false); + using var heartbeat = new PeriodicTimer(HeartbeatInterval); try