fix(flow-chat): eliminate excess footer whitespace after tool-card collapses (#1176)#1181
Merged
Conversation
…afety When multiple subagents are dispatched and one must wait for another to finish (e.g., a read-only FileFinder followed by a read-write GeneralPurpose), the second tool previously appeared to be
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.
Problem
GitHub issue #1176: After multi-turn conversations with tool-card collapses, an excessive whitespace gap accumulates at the bottom of the message list. The gap grows with each streaming cycle and never shrinks.
Root Cause
The collapse protection system relied on \layoutTransitionCountRef\ (CSS \ ransitionrun/\ ransitionend\ event counting) as the sole signal for blocking compensation consumption. When Virtuoso virtualization removed DOM nodes mid-transition, \ ransitionend/\ ransitioncancel\ events were lost, leaving the counter permanently stuck above zero. This blocked ALL consumption paths for \collapse.px, causing the synthetic footer reservation to grow without bound.
Solution
Replaced the transition-event system entirely with \pendingCollapseIntentRef\ (state-based, with \expiresAtMs\ timestamp) as the sole collapse protection signal. No timers, no event counting — all state changes are observable and deterministic.
Three accumulation paths for \collapse.px\ are now sealed:
1. Collapse intent expiry drain
eplayDeferredFollowIfSettled()\ (called on every scroll event) detects when \pendingCollapseIntentRef.expiresAtMs\ passes and immediately drains residual \collapse.px\ to zero. Previously, expired intents left stale compensation that could only be consumed by future content growth or user scroll — which may never happen.
2. Unsignaled shrink ratchet fix
\measureHeightChange's unsignaled shrink path used \Math.max(currentCollapseCompensation, fallbackRequiredCollapseCompensation), creating a ratchet where \collapse.px\ could only grow, never shrink. Replaced with just \allbackRequiredCollapseCompensation\ so the compensation tracks the actual need.
3. Input shrink drain without streaming
The \useLayoutEffect\ that protects against input-footer shrink added to \collapse.px\ but relied on streaming content growth to consume it. If no streaming followed (e.g., quick turn completion), the residual persisted. Now schedules a
equestAnimationFrame\ drain when !isStreamingOutputRef, guarded by \pendingCollapseIntentRef.active\ to avoid racing with an ongoing tool-card collapse transition.
Audit
An adversarial third-party review identified and addressed a medium-risk race condition in the initial Issue 3 fix: the rAF drain lacked a guard for active collapse intents. This was fixed before commit — the rAF now checks \pendingCollapseIntentRef\ and skips drain if a transition is still being protected.
Residual edge cases acknowledged (low risk):
Verification
\
pnpm run type-check:web ✓ (0 errors)
E2E tests (4 specs) ✓ (4 passing, 17.9s)
\\
E2E coverage:
Files Changed