fix(chat): flex-column ChatLayout root so the sticky dock doesn't add phantom scroll height (#2573)#4202
Open
jiunshinn wants to merge 2 commits into
Open
Conversation
… phantom scroll height (facebook#2573)
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
github-actions
Bot
requested review from
cvkxx,
ernestt,
kentonquatman and
rubyycheung
July 22, 2026 15:09
Contributor
PR Analysis ReportModified ComponentsChat
Bundle Size Summary
Accessibility AuditStatus: 1 accessibility violation(s) found — 1 serious. Chat - 1 issue(s)
Generated by PR Enrichment workflow | View full report |
jiunshinn
marked this pull request as ready for review
July 22, 2026 15:43
jiunshinn
requested review from
cixzhang,
ejhammond and
imdreamrunner
as code owners
July 22, 2026 15:43
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
In self-scrolling mode (no external
scrollRef),ChatLayoutalways overflows its scroll container by exactly the composer/dock height, producing a phantom vertical scrollbar even when the messages are far shorter than the available height. Reported with Playwright measurements on the unmodifiedai-chatdocsite template in #2573.Root cause
The root is the scroll container (
overflowY: auto) with two in-flow children:messageArea—minHeight: 100%(+paddingBlockEnd)dockContainer—position: sticky; bottom: 0position: stickyelements still occupy space in normal flow. So total content height =messageArea (≥ 100% of the container)+dock height— the root always overflows by the dock height, regardless of message count.Fix
Third option from the issue: make the root a flex column and let the message area flex instead of forcing its own height.
root:display: flex; flexDirection: columnmessageArea:minHeight: 100%→flexGrow: 1; flexShrink: 0; flexBasis: autodockContainer:flexShrink: 0(keeps its natural height as a flex item; inert in fixed mode sinceposition: fixedtakes it out of flex layout)With short content the message area grows to fill exactly
100% − dockHeight, so the sticky dock's natural height is part of the 100% and there is nothing to scroll. With long contentflexShrink: 0+flexBasis: autokeep the message area at its content height, the total exceeds the container, and self-scroll works exactly as before — sticky positioning is unaffected by flex layout, so the dock still pins to the scrollport bottom.Why not the other two options
minHeight: calc(100% − dockHeight)— the dock's height is content-driven (the composer grows with multiline input), so the CSS var would need JS measurement (ResizeObserver) or a magic constant. The project rule is CSS-native layout; flex computes the same subtraction for free.bottom: 0resolves against the padding box, not the scrollport), so the dock would not stay pinned while scrolling long conversations. Making it work would mean hoisting the dock outside the scroll container — a larger DOM restructure touching root ref semantics,xstyle, and blur-layer stacking.Measurements (real browser, before → after)
No Playwright harness exists in the repo, so I verified with a static harness: the built
distbundle (esbuild-bundled with React) +dist/astryx.cssrendered in headless Chrome (agent-browser CLI, viewport 1280×720),ChatLayoutinside a 600px-tall flex host, defaultChatComposer. Dock height in this harness is 150px (the reporter's 186px came from the docsite template's taller composer — the invariant isdiff == dockHeight).minHeight: 100%)Non-regression checks in the same harness, after the fix:
scrollTop = 0, the dock stays pinned to the scrollport bottom (dockRect.bottom == rootRect.bottom).scrollTop3208 == max scroll —useChatStreamScrollunaffected.scrollRefmode (40 msgs,document.scrollingElement): dock isposition: fixedpinned to the viewport bottom, root itself does not scroll (scrollHeight − clientHeight == 0on root), page scrolls and auto-scrolls to bottom (1911 == max) —dockContainerFixedpath unchanged.Test plan
pnpm exec vitest run packages/core/src/Chat— 149/149 pass.ChatLayout — self-scroll layout contract (#2573)tests assert the class-level intent (jsdom can't do real layout): root isflex/column, message area hasflexGrow: 1; flexShrink: 0and nominHeight: 100%, dock isstickyin self-scroll mode andfixedwith an externalscrollRef. Verified the new tests fail (2/3) against the unfixed component; the third asserts the unchanged sticky/fixed switching.tsc --noEmit -p packages/core, eslint,check:sync,check:changesets,check:package-boundaries— all pass.Fixes #2573
Reviewer questions
flexShrink: 0ondockContaineris shared by both modes (inert underposition: fixed). Would you rather it live ondockContainerStickyto keep the fixed-mode style set minimal?flexGrow/flexShrink/flexBasis) for explicitness next to the#2573comment; happy to collapse toflex: '1 0 auto'if you prefer the shorthand.align/anchorprop for top-aligned message lists #2572 is not addressed here, but the flex-column root probably makes amarginBlockStart: auto-style fix easier there — want me to follow up on that separately?