Apply urgent realtime thread changes without flushing the debounce buffer - #2279
Open
vburojevic wants to merge 1 commit into
Open
Apply urgent realtime thread changes without flushing the debounce buffer#2279vburojevic wants to merge 1 commit into
vburojevic wants to merge 1 commit into
Conversation
…ffer Any thread message containing an immediate change kind (status-changed, history-rewritten, environment-changed, tabs-changed) flushed the entire buffered invalidation state, including every debounced events-appended timeline invalidation for every thread. The queued-message send path publishes [events-appended, queue-changed, status-changed] as one message, so during streaming the 50/200ms coalescing window collapsed exactly when it mattered, and one thread's status flip flushed every other streaming thread's timeline invalidations with it. Partition each message by the registry's flush priority: immediate kinds run their dirty handlers synchronously against that message alone, and debounced kinds stay in the scheduler. Turn completion is exempt: when a message's eventTypes include turn/completed, every kind is recorded and the buffer flushes at once (the old path), because the lifecycle publish bundles the final events-appended with the status flip and splitting them would re-enable the composer up to a debounce window before the final assistant text renders; a completed stream needs no coalescing protection. Hidden-document deferral is unchanged. The immediate path routes message metadata through mergeThreadChangeMetadata so its context (including get-bb#2169's statusChange row snapshot) cannot drift from the buffered flush path, and passes metadata only when the message carries a thread id: an id-less global message runs its handlers with undefined metadata exactly like the flush's global path, so a stray projectId cannot narrow dirtyActiveThreadListQueries to one project. Behavior change: only mid-stream bundles are partitioned, so a status flip applies immediately while the timeline invalidation keeps its coalescing window (up to 50ms); the turn-completion supersede stays atomic. The "once per flush" search test now drives its two completions through the hidden-document merge, the one place completions still coalesce into a single flush. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What was wrong
Any thread message containing an immediate change kind flushed the entire buffered invalidation state — including every debounced events-appended timeline invalidation for every thread. The queued-message send path publishes [events-appended, queue-changed, status-changed] as one message, so during streaming the 50/200ms coalescing collapsed exactly when it mattered, and one thread's status flip flushed every other streaming thread's buffer.
What changed
Partition each visible message by the registry's flush priority: immediate kinds run their dirty handlers synchronously against that message alone (threading #2169's statusChange snapshot); debounced kinds stay in the scheduler. Turn-completion bundles (eventTypes includes turn/completed) still flush atomically — a completed stream needs no storm protection, so composer re-enable and final text stay in sync. Hidden-document path unchanged. Immediate metadata is id-gated and routed through mergeThreadChangeMetadata so global broadcasts keep broad invalidation.
Behavior change
Mid-stream bundles only: their timeline invalidation waits ≤50ms (200ms max) instead of jumping the queue. Turn completion is unchanged (atomic).
How verified
65/65 realtime-cache-effects + cache-owner tests, including new: bundled-publish debounce hold, cross-thread isolation, bare status-changed snapshot refresh (#2169 invariant), id-less broadcast breadth, atomic completion. Each fails without its fix. Typecheck + oxlint clean; adversarially reviewed.
🤖 Generated with Claude Code