Fullscreen auto-scroll: submitting a message doesn't re-arm sticky follow, so streaming replies scroll off the bottom
Summary
In fullscreen mode, the conversation view reliably auto-scrolls to the bottom on session jump and session switch, but not after submitting a message. When it fails, streaming assistant output grows below the viewport and the reply appears cut off — you have to scroll down manually to read it.
It's intermittent, and the trigger appears to be whether you scrolled up at any point before hitting enter (e.g. scrolling back to re-read the previous reply while composing).
Environment
- Claude Code 2.1.224
- Windows 11 Pro (10.0.26100), desktop app
- Fullscreen mode on
autoScrollEnabledunset → resolving to its default oftrueCLAUDE_CODE_DISABLE_VIRTUAL_SCROLLunset
Steps to reproduce
- Open a session with enough history to be scrollable, in fullscreen mode.
- Scroll up a little — a single mouse-wheel nudge is enough — to re-read the previous reply.
- Type a message and submit it without scrolling back down first.
- Watch the assistant reply stream.
Expected: the view follows the streaming output, as it does after a session jump or switch.
Actual: the view stays put. The reply grows below the fold and reads as truncated until you scroll down by hand.
If you never touch the wheel between replies, it follows correctly — which is why the bug presents as intermittent.
Analysis
I traced this through the shipped bundle. Sharing it in case it saves triage time; treat it as a hypothesis from static reading rather than a verified fix.
Content-growth following is gated on this condition (deminified for readability):
const sticky = state.stickyScroll ?? Boolean(attributes.stickyScroll);
const extent = sticky ? height : Math.max(state.scrollHeightHwm ?? 0, height);
const maxTop = Math.max(0, extent - viewportHeight);
const grew = newHeight >= height;
const follow = attributes.followGrowth !== false;
if ((sticky || (attributes.stickyScroll !== false && follow && grew && scrollTop >= maxTop)) && ...)
So growth is followed if either the sticky flag is set, or you are already parked at/past maxTop. If sticky is false and you are even slightly above the bottom, growth is not followed at all.
Two things then combine:
- Submit doesn't appear to re-arm sticky. There are 7
scrollToBottom()call sites in the bundle — theEndkey, thescroll:bottomcommand, the top/bottom dispatch cases, and this gated helper:
``js``
if (getSetting("autoScrollEnabled", true)) view.scrollToBottom(); // sets stickyScroll = true
else view.scrollTo(view.getScrollHeight() - view.getViewportHeight()); // one-shot, sticky stays false
Session jump and switch route through an explicit scrollToBottom(), which sets sticky and clears the high-water mark — matching the fact that those two always work. I could not find a call site corresponding to message submit; submit seems to inherit whatever sticky state the user was left in. This is the one claim resting on absence of evidence.
- The high-water mark makes the fallback test harder to pass. When sticky is false,
extentis derived fromscrollHeightHwm— the tallest the transcript has ever been — rather than its current height.maxTopis therefore measured against a stale, larger value, soscrollTop >= maxTopcan fail even when the user is visually at the bottom. Sticky-clearing paths (scrollTo,scrollBy,scrollToElement) reset the mark, butscrollTo(top, { preserveHwm: true })deliberately retains it.
Anything that clears sticky — scrollBy (mouse wheel), scrollTo, scrollToElement — therefore leaves the next submit unfollowed.
Possibly relevant: the codebase already emits a scrolledBeforeSubmit field alongside isFullscreen in the tengu_return_to_session telemetry event, which suggests scroll position at submit time is already a suspected problem area.
Suggested fix
Route message submit through the same gated scrollToBottom() helper that session jump and switch use, so submitting re-arms sticky follow (and clears the high-water mark) rather than inheriting a stale unstuck state.
Workaround
Press End immediately before or after submitting — it's a direct scrollToBottom() call site, so it re-arms sticky follow instead of performing a dead one-shot jump.