| ... | @@ -187,15 +187,34 @@ function EventRouter() { | ... | @@ -187,15 +187,34 @@ function EventRouter() { |
| 187 | | 187 | |
| 188 | void syncSnapshot().catch(() => undefined); | 188 | void syncSnapshot().catch(() => undefined); |
| 189 | | 189 | |
| | 190 | // Batch domain events: collect over a short window then sync once. |
| | 191 | const DOMAIN_EVENT_BATCH_MS = 100; |
| | 192 | let batchTimer: ReturnType<typeof setTimeout> | null = null; |
| | 193 | let needsProviderInvalidation = false; |
| | 194 | |
| | 195 | const flushBatch = () => { |
| | 196 | batchTimer = null; |
| | 197 | if (needsProviderInvalidation) { |
| | 198 | needsProviderInvalidation = false; |
| | 199 | void queryClient.invalidateQueries({ queryKey: providerQueryKeys.all }); |
| | 200 | } |
| | 201 | void syncSnapshot(); |
| | 202 | }; |
| | 203 | |
| 190 | const unsubDomainEvent = api.orchestration.onDomainEvent((event) => { | 204 | const unsubDomainEvent = api.orchestration.onDomainEvent((event) => { |
| 191 | if (event.sequence <= latestSequence) { | 205 | if (event.sequence <= latestSequence) { |
| 192 | return; | 206 | return; |
| 193 | } | 207 | } |
| 194 | latestSequence = event.sequence; | 208 | latestSequence = event.sequence; |
| 195 | if (event.type === "thread.turn-diff-completed" || event.type === "thread.reverted") { | 209 | if (event.type === "thread.turn-diff-completed" || event.type === "thread.reverted") { |
| 196 | void queryClient.invalidateQueries({ queryKey: providerQueryKeys.all }); | 210 | needsProviderInvalidation = true; |
| | 211 | } |
| | 212 | // Throttle (not debounce): schedule a flush if none is pending. |
| | 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); |
| 197 | } | 217 | } |
| 198 | void syncSnapshot(); | | |
| 199 | }); | 218 | }); |
| 200 | const unsubTerminalEvent = api.terminal.onEvent((event) => { | 219 | const unsubTerminalEvent = api.terminal.onEvent((event) => { |
| 201 | const hasRunningSubprocess = terminalRunningSubprocessFromEvent(event); | 220 | const hasRunningSubprocess = terminalRunningSubprocessFromEvent(event); |
| ... | @@ -280,6 +299,9 @@ function EventRouter() { | ... | @@ -280,6 +299,9 @@ function EventRouter() { |
| 280 | }); | 299 | }); |
| 281 | return () => { | 300 | return () => { |
| 282 | disposed = true; | 301 | disposed = true; |
| | 302 | if (batchTimer !== null) { |
| | 303 | clearTimeout(batchTimer); |
| | 304 | } |
| 283 | unsubDomainEvent(); | 305 | unsubDomainEvent(); |
| 284 | unsubTerminalEvent(); | 306 | unsubTerminalEvent(); |
| 285 | unsubWelcome(); | 307 | unsubWelcome(); |