Skip to content

fix(flow-chat): drain stuck bottom whitespace after subagent card collapses - #2159

Open
YodonTan wants to merge 3 commits into
GCWing:mainfrom
YodonTan:fix/subagent-wrap-blank-space
Open

fix(flow-chat): drain stuck bottom whitespace after subagent card collapses#2159
YodonTan wants to merge 3 commits into
GCWing:mainfrom
YodonTan:fix/subagent-wrap-blank-space

Conversation

@YodonTan

@YodonTan YodonTan commented Aug 7, 2026

Copy link
Copy Markdown

Fixes #2154

Problem

After a subagent (Task) card collapses, the chat viewport keeps a large block of blank space below the last message. The blank area grows with each subagent card event and only drains after manually scrubbing the scroll wheel (each round trip consumes only the upward-scroll distance). The scrollbar thumb sits at the physical bottom while hundreds of pixels of empty background separate the content from the input bar.

Root cause

Collapse-intent pre-compensation adds provisional footer space (collapse.px) before the card shrinks. When the user is at the physical bottom, every settlement path dead-locks:

  • settleCollapseReservationForViewport / retained quiet settlement compute the geometric minimum for the current scrollTop, but the synthetic footer itself holds scrollTop at the physical bottom, so the minimum recomputes exactly the current reservation (e.g. 786px stays 786px).
  • The scroll handler's per-scroll consumption never fires at the physical bottom because scrollDelta is 0 (scrollTop is capped by the footer).
  • The idle reservation audit settles to the same value, so it cannot help either.

Multiple collapse intents (e.g. several parallel subagent failures) stack provisional estimates on the unsettled reservation (base = current total compensation), which is why the whitespace grows monotonically during the session. Repeated up/down scrub cycles only consumed the upward-scroll distance per round trip.

Changes

  • convergeBottomReservationsToContentBottom: when the scroller sits at the physical bottom (within 2px) with no preserving-element / pinned-item viewport owner and no active collapse intent, clear collapse + pin reservations in one synchronous footer update and fall scrollTop to the new content bottom (the delayed browser clamp that compensation was hiding). Wired into:
    • retained quiet settlement (settleRetainedCollapseRange)
    • collapse-intent finalization (non-pinned strategies)
    • the idle reservation audit - bottom users only; mid-list readers keep their reading anchor (the audit no longer releases a preserved element anchor for mid-list readers)
    • the scroll handler: any scroll event at the physical bottom is bottom intent - one pass replaces per-scrub consumption
    • the collapse-intent accumulation guard: a new provisional estimate no longer stacks on a settled bottom reservation
    • the input-stack-shrink drain
  • Bound collapse-intent finalize retries (max 20): a virtualized sticky-pin target can no longer keep the intent active (and auto-follow suspended) forever.
  • Intent-window growth consumption is capped at the measured protection line so subagent growth no longer sediments into footer whitespace.
  • CSS: overflow-wrap: anywhere + min-width: 0 on subagent projection text and task card headers so long unbroken tokens wrap inside the card instead of widening the layout.

Tests

New component tests in VirtualMessageList.session-boundary.test.tsx:

  • bottom convergence on finalize / scroll / idle audit at the physical bottom
  • negative cases: mid-list readers, pinned-item and preserving-element viewports must NOT clear reservations
  • consecutive collapse intents do not stack provisional estimates
  • growth during an active collapse intent only consumes provisional inflation (protection line kept)
  • bounded force-settle to the geometric minimum at mid-list
  • data-reservation-px footer diagnostics

All flow_chat modern + tool-cards tests pass (376), type-check:web and ESLint are clean.

Manual verification

Verified with a real streaming session: long-title subagent failures collapse without leaving bottom whitespace, repeated failures no longer accumulate blank space, and the viewport settles to the content bottom without scrub-wheel workarounds.

Tant added 3 commits August 7, 2026 00:41
…hitespace

Add gated flow_chat_diagnostics probes that make a growing bottom blank
attributable to its reservation owner:

- Periodic bottom-reservation ledger sampling (collapse/pin px+floor,
  intent state, owner kind, coordinator mode, follow/streaming flags,
  scroller geometry) while the logging setting is enabled
- Collapse-intent finalize retry counting plus a trace when the drain
  target is not renderable
- Auto-follow suspension duration probe (fires once after 2s)
- Subagent/task card ResizeObserver height-change correlation probes
- Anchor-restore bottom-range additions tracked per source in the
  viewport coordinator
- footer dataset.reservationPx so DevTools shows the synthetic tail
  space at a glance
- flowChatDiagnostics.subscribe() so sampling can start/stop when the
  setting toggles at runtime
Long unbroken tokens (file paths, model names, URLs) inside the subagent
projection text and task card headers could stretch the min-content width of
the card and change the layout height. Add overflow-wrap:anywhere and
min-width:0 so wrapping happens inside the card instead of widening the
layout.
Subagent card collapses pre-compensate the footer with provisional tail
space. At the physical bottom every settle variant recomputes exactly the
current reservation as its geometric minimum (the synthetic footer holds
scrollTop high), so collapse compensation could only drain through content
growth or repeated up/down scrub cycles - the reported growing bottom
whitespace that occasionally cleared on scroll.

Introduce convergeBottomReservationsToContentBottom: when the scroller sits
at the physical bottom with no preserving/pinned viewport owner and no active
collapse intent, clear collapse + pin reservations in one synchronous footer
update and fall scrollTop to the new content bottom. Wire it into retained
quiet settlement, collapse-intent finalization (non-pinned strategies), the
idle reservation audit, the scroll handler (any scroll event at the physical
bottom is bottom intent), the collapse-intent accumulation guard (a new
provisional estimate no longer stacks on a settled bottom reservation), and
the input-stack-shrink drain. The idle audit only releases a preserved
element anchor for bottom users; mid-list readers keep their reading anchor.

Also bound collapse-intent finalize retries (20 max) so a virtualized sticky
pin target cannot keep the intent (and auto-follow suspension) alive forever,
and cap intent-window growth consumption at the measured protection line.
Tests cover growth protection, convergence positive/negative cases, force
settle at mid-list, and the stale-pin audit path.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

FlowChat: bottom whitespace grows after subagent card collapses and only drains on manual scrolling

1 participant