authorgravatar for julius0216@outlook.comJulius Marminge <julius0216@outlook.com> 2026-03-09 12:45:34-07:00
committergravatar for jmarminge@gmail.comJulius Marminge <jmarminge@gmail.com> 2026-03-09 14:02:29-07:00
loge765051f148bf6006e30118eb47f5d33ea9e32be
treea78bc9ecd1a220d5bfe924b44338b205738e88ad
parentf5e4df5cab247021501876358fd72c7196cd96b9

Use React Pacer throttler for domain event snapshot sync

- Replace manual timeout-based domain event batching with `Throttler` - Keep provider query invalidation batched with trailing 100ms flushes - Cancel throttler and reset invalidation flag during EventRouter cleanup

1 files changed, 19 insertions(+), 24 deletions(-)

apps/web/src/routes/__root.tsx+19-24
...@@ -8,6 +8,7 @@ import {...@@ -8,6 +8,7 @@ import {
8} from "@tanstack/react-router";8} from "@tanstack/react-router";
9import { useEffect, useRef } from "react";9import { useEffect, useRef } from "react";
10import { QueryClient, useQueryClient } from "@tanstack/react-query";10import { QueryClient, useQueryClient } from "@tanstack/react-query";
11import { Throttler } from "@tanstack/react-pacer";
1112
12import { APP_DISPLAY_NAME } from "../branding";13import { APP_DISPLAY_NAME } from "../branding";
13import { Button } from "../components/ui/button";14import { Button } from "../components/ui/button";
...@@ -150,6 +151,7 @@ function EventRouter() {...@@ -150,6 +151,7 @@ function EventRouter() {
150 let latestSequence = 0;151 let latestSequence = 0;
151 let syncing = false;152 let syncing = false;
152 let pending = false;153 let pending = false;
154 let needsProviderInvalidation = false;
153155
154 const flushSnapshotSync = async (): Promise<void> => {156 const flushSnapshotSync = async (): Promise<void> => {
155 const snapshot = await api.orchestration.getSnapshot();157 const snapshot = await api.orchestration.getSnapshot();
...@@ -185,21 +187,20 @@ function EventRouter() {...@@ -185,21 +187,20 @@ function EventRouter() {
185 syncing = false;187 syncing = false;
186 };188 };
187189
188 void syncSnapshot().catch(() => undefined);190 const domainEventFlushThrottler = new Throttler(
189191 () => {
190 // Batch domain events: collect over a short window then sync once.192 if (needsProviderInvalidation) {
191 const DOMAIN_EVENT_BATCH_MS = 100;193 needsProviderInvalidation = false;
192 let batchTimer: ReturnType<typeof setTimeout> | null = null;194 void queryClient.invalidateQueries({ queryKey: providerQueryKeys.all });
193 let needsProviderInvalidation = false;195 }
194196 void syncSnapshot();
195 const flushBatch = () => {197 },
196 batchTimer = null;198 {
197 if (needsProviderInvalidation) {199 wait: 100,
198 needsProviderInvalidation = false;200 leading: false,
199 void queryClient.invalidateQueries({ queryKey: providerQueryKeys.all });201 trailing: true,
200 }202 },
201 void syncSnapshot();203 );
202 };
203204
204 const unsubDomainEvent = api.orchestration.onDomainEvent((event) => {205 const unsubDomainEvent = api.orchestration.onDomainEvent((event) => {
205 if (event.sequence <= latestSequence) {206 if (event.sequence <= latestSequence) {
...@@ -209,12 +210,7 @@ function EventRouter() {...@@ -209,12 +210,7 @@ function EventRouter() {
209 if (event.type === "thread.turn-diff-completed" || event.type === "thread.reverted") {210 if (event.type === "thread.turn-diff-completed" || event.type === "thread.reverted") {
210 needsProviderInvalidation = true;211 needsProviderInvalidation = true;
211 }212 }
212 // Throttle (not debounce): schedule a flush if none is pending.213 domainEventFlushThrottler.maybeExecute();
213 // This guarantees at most one syncSnapshot per batch window
214 // without starving the UI during sustained event bursts.
215 if (batchTimer === null) {
216 batchTimer = setTimeout(flushBatch, DOMAIN_EVENT_BATCH_MS);
217 }
218 });214 });
219 const unsubTerminalEvent = api.terminal.onEvent((event) => {215 const unsubTerminalEvent = api.terminal.onEvent((event) => {
220 const hasRunningSubprocess = terminalRunningSubprocessFromEvent(event);216 const hasRunningSubprocess = terminalRunningSubprocessFromEvent(event);
...@@ -299,9 +295,8 @@ function EventRouter() {...@@ -299,9 +295,8 @@ function EventRouter() {
299 });295 });
300 return () => {296 return () => {
301 disposed = true;297 disposed = true;
302 if (batchTimer !== null) {298 needsProviderInvalidation = false;
303 clearTimeout(batchTimer);299 domainEventFlushThrottler.cancel();
304 }
305 unsubDomainEvent();300 unsubDomainEvent();
306 unsubTerminalEvent();301 unsubTerminalEvent();
307 unsubWelcome();302 unsubWelcome();