From e0041ccd4bf3e54862c167dd4a0afe12b7546361 Mon Sep 17 00:00:00 2001 From: ciregenz Date: Tue, 4 Aug 2026 10:20:16 -0700 Subject: [PATCH] [eric] ws: a live card drag dams the message queue, streaming renders wait for the pointer to settle --- frontend/src/shared/ws/WebSocketManager.ts | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/frontend/src/shared/ws/WebSocketManager.ts b/frontend/src/shared/ws/WebSocketManager.ts index 61484255..cd297690 100644 --- a/frontend/src/shared/ws/WebSocketManager.ts +++ b/frontend/src/shared/ws/WebSocketManager.ts @@ -121,6 +121,9 @@ class WebSocketManager { WebSocketManager._flushTimer = setTimeout(WebSocketManager._flushMessages, 250); } + // First moment a flush was deferred for a live drag; bounds the damming so a stuck drag class can't buffer forever. + private static _dragDeferredAt = 0; + private static _flushMessages = () => { if (!WebSocketManager._flushScheduled) return; // the other trigger already drained this batch WebSocketManager._flushScheduled = false; @@ -129,6 +132,18 @@ class WebSocketManager { WebSocketManager._flushTimer = null; } if (WebSocketManager._messageQueue.length === 0) return; + // A live card drag owns the main thread: WS-driven renders mid-drag are what made dragging a + // working agent feel laggy, and nobody reads streaming tokens while holding a card. Buffer until + // the pointer settles, hard-capped by time and queue depth. + if (document.body.classList.contains('dashboard-marquee-active')) { + if (!WebSocketManager._dragDeferredAt) WebSocketManager._dragDeferredAt = Date.now(); + if (Date.now() - WebSocketManager._dragDeferredAt < 2000 && WebSocketManager._messageQueue.length < 500) { + WebSocketManager._flushScheduled = true; + WebSocketManager._flushTimer = setTimeout(WebSocketManager._flushMessages, 100); + return; + } + } + WebSocketManager._dragDeferredAt = 0; const batch = WebSocketManager._messageQueue; WebSocketManager._messageQueue = []; // unstable_batchedUpdates collapses all dispatches inside the callback into a single React render. Available in React 17; React 18's automatic batching covers this too, but explicit wrap remains correct in both and protects against future batching-context changes.