fix(desktop): stabilize native transcript scrolling / 修复聊天区原生滚动跳页与卡顿 - #8264
Merged
SivanCola merged 6 commits intoAug 11, 2026
Merged
Conversation
Problem: On macOS, two-finger scrolling the chat content area felt sticky and jumpy—especially over markdown tables—because nested overflow containers latched the trackpad and virtualizer/stream rewrites fought native inertia mid-gesture. Root cause: CSS overflow-x:auto promotes overflow-y to auto, so ordinary tables/code blocks became vertical scroll ports; virtualizer height compensation and stream tail-follow could still write scrollTop while the user was gesturing. Fix: Add a short user-gesture lock that blocks compensating scroll writes, promote edge/non-scrollable nested wheels to the transcript with latching handoff, keep small tables and code blocks out of nested vertical scroll, and mark large virtual tables for handoff. Verification: npx tsx scroll-manager, transcript-scroll-session, nested-scroll-handoff, markdown-table-virtual, typography-overflow-contract, and transcript-virtualization tests.
Problem: Large markdown tables still introduced nested vertical scroll ports by default, and virtualizer remeasure could jump after a trackpad gesture settled. Root cause: Oversized tables mounted a max-height virtual scroller immediately, and height compensation was deferred only mid-gesture, not batched once after the hold window. Fix: Collapse tables above the virtual threshold to a 12-row document-flow preview with Expand all / Collapse; expanded tables keep the nested virtual scroller with handoff. After gesture idle, measure once and reconcile the reading anchor. Verification: npx tsx markdown-table-virtual, scroll-manager, transcript-scroll-session, and nested-scroll-handoff tests.
Problem: PR esengine#8264 was based on an older main-v2 transcript implementation. Root cause: Selection and transcript virtualization work merged after the contributor branch diverged. Fix: Merge the current base before completing scroll-session hardening so all fixes target the live integration surface. Verification: Conflicts were resolved semantically and both the existing selection behavior and the new scroll-session contracts were verified by the follow-up test commit.
Problem: Long variable-height transcripts could stall or jump while trackpad inertia, native scrollbar input, or Windows middle-button auto-scroll overlapped virtualizer and resize compensation. Creation scrollbar sensitivity could also change while rows mounted. Root cause: Scroll ownership only covered wheel, touch, and keyboard intent; treated unowned native scroll events ambiguously; remeasured the entire virtualizer after every idle gesture; and recomputed custom-scrollbar geometry during a drag. A stale scrollend could also settle a newer gesture. Fix: Model native input sessions explicitly, arbitrate controller-owned scroll events, normalize wheel delta modes, guard stale scrollend, freeze custom-scrollbar drag geometry, and measure only real layout invalidations with anchor reconciliation. Split interaction and scrollbar concerns out of Transcript and add real-browser stability coverage. Verification: - pnpm --dir desktop/frontend test:transcript - pnpm --dir desktop/frontend test:transcript-browser - pnpm --dir desktop/frontend typecheck - pnpm --dir desktop/frontend test:typecheck - pnpm --dir desktop/frontend test:motion - pnpm --dir desktop/frontend build - go run ./tools/repolint
Problem: A short upward wheel, trackpad, native scrollbar, or Creation scrollbar movement could remain inside the 80px near-bottom band. After the gesture quiet window, streaming output could therefore re-enable tail-follow and pull the viewport back down. Root cause: The scroll manager treated geometric proximity as user intent and recomputed stick mode from the near-bottom threshold after every native scroll event. Fix: Latch explicit user detachment across gesture settlement, keep resize and stream repins disabled while detached, and restore tail-follow only at the physical bottom or through an explicit bottom/reset action. Apply the same contract to the Creation scrollbar. Verification: - pnpm --dir desktop/frontend test:transcript - pnpm --dir desktop/frontend test:transcript-browser - pnpm --dir desktop/frontend test:typecheck - pnpm --dir desktop/frontend test:motion - pnpm --dir desktop/frontend build - go run ./tools/repolint
Problem: - Nested downward handoff at the physical bottom detached streaming tail-follow. - Passive tail writes rejected during an active gesture were dropped, so final token and row-size growth could leave the viewport behind. Root cause: - Nested handoff discarded wheel direction and always released auto-scroll. - Gesture arbitration had no generation-aware replay for blocked passive owners. Fix: - Propagate normalized nested deltas and release only for upward or detached reading intent. - Replay blocked stream, resize, footer, and row-size repins once after gesture idle using the current scrollHeight. - Cancel deferred work on manual intent, selection, explicit navigation, and tab generation changes. - Adapt esengine#8291's row-size ownership, pinned measurement rule, and stream-end fallback into the unified gesture controller. - Add deterministic and real Chromium regression coverage. Verification: - pnpm test:transcript - pnpm test:transcript-browser - pnpm test:typecheck - pnpm test:motion - pnpm build - go run ./tools/repolint Co-authored-by: LI Xinyu <Lxy1962@bupt.edu.cn>
|
确实经常遇到这个问题,修复的很快,赞! |
This was referenced Aug 11, 2026
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.
Summary
Long variable-height transcripts could feel sticky, reverse direction, jump by a virtual page, or stop following the final answer when native scrolling overlapped TanStack measurement compensation, streaming tail-follow, or resize repins. This PR makes native input a first-class scroll owner, keeps compensating writers silent until the gesture has actually settled, and replays a still-valid tail repin once after idle instead of dropping it.
It also preserves the original large-table and nested-scroll improvements while integrating the latest
main-v2transcript selection and virtualization work.User-visible changes
scrollend, RAF, timer, resize, row-measurement, and deferred-repin work cannot affect a newer tab generation.deltaModevalues are normalized for pixel, line, and page devices before nested-scroll handoff.Implementation
transcriptScrollSessionwith explicit wheel, touch, keyboard, native-scroll, nested-scroll, and middle-button ownership.useScrollManager; only upward or already-detached intent releases auto-scroll.stream,container-resize,footer-resize, androw-sizeowners. Rejected passive writes replay once at gesture idle using the currentscrollHeightonly if tail-follow is still valid.useScrollManager.scrollendas a guarded hint with a fallback quiet window; stale events are rejected.row-sizeowner, disables anchor compensation while pinned, and frame-batches measured row growth into the tail-follow path.Transcript.tsxinto focused hooks.Consolidation and repository contributions
2cae44a0dincludes effective Git co-author attribution.Compatibility and security
Verification
pnpm --dir desktop/frontend test:transcriptpnpm --dir desktop/frontend test:transcript-browserpnpm --dir desktop/frontend test:typecheckpnpm --dir desktop/frontend test:motionpnpm --dir desktop/frontend buildgo run ./tools/repolintgit diff --checkpnpm --dir desktop/frontend test:bench— scroll-path gates pass; two repository-level baseline gates remain red as described below.Real Chromium assertions include:
scrollTopjump afterscrollend;tail-follow, then replays once to bottom distance 0 after idle;Production bundle results:
Repository-wide benchmark note: the latest branch run reports 303.8ms switch P95 (3.8ms above the 300ms gate) and 46.7 MiB retained-heap growth. The latest same-machine
main-v2comparison reported 305ms and 46.4 MiB, respectively, so these existing benchmark failures are not attributed to this scroll change. Scroll-path evidence remains bounded: 24ms input P95, 0% DOM growth, and no observed long tasks. Native macOS Wails/WebKit and Windows Wails/WebView2 manual shell verification is still recommended before merge.Documentation impact
Documentation-impact: none - this repairs existing transcript scrolling behavior without changing the documented user workflow or configuration.