Skip to content

[Fix] Replace per-token forced reflow with ResizeObserver and fix Virtuoso dual scroll context#196

Merged
samzong merged 1 commit intomainfrom
fix/chatview-scroll
Mar 27, 2026
Merged

[Fix] Replace per-token forced reflow with ResizeObserver and fix Virtuoso dual scroll context#196
samzong merged 1 commit intomainfrom
fix/chatview-scroll

Conversation

@samzong
Copy link
Copy Markdown
Collaborator

@samzong samzong commented Mar 27, 2026

Summary

ChatView had two scroll architecture flaws: (1) scrollToBottom fired on every streaming token via useEffect on activeTurn?.streamingText, causing synchronous scrollTop = scrollHeight forced reflow ~30 times/second; (2) Virtuoso nested inside overflow-y-auto created competing scroll contexts, with StreamingMessage rendered outside Virtuoso's scroll ownership.

Type of change

  • [Fix] bug fix

Why is this needed?

Per-token forced reflow causes visible jank on mobile during streaming. The dual scroll context means streaming content may be clipped or unreachable when virtualization is active (≥100 messages).

What changed?

  • Replaced scrollToBottom callback + useEffect depending on activeTurn?.streamingText with a single ResizeObserver on a content wrapper div — zero coupling to React data flow, fires at most once per frame
  • Extracted tail content (StreamingMessage, thinking indicator, connecting status) into ChatTail component whose prop shape { context?: TailContext } directly matches Virtuoso's Footer API
  • Non-virtualized path: overflow-y-auto + scrollRef + contentRef wrapper observed by ResizeObserver
  • Virtualized path: overflow-hidden, no scrollRef; tail content rendered via Virtuoso components.Footer so followOutput accounts for streaming height changes
  • VIRTUOSO_COMPONENTS defined at module level for stable component identity (prevents unmount/remount per render)

Architecture impact

  • Owning layer: renderer (PWA)
  • Cross-layer impact: none
  • Invariants touched from docs/architecture-invariants.md: none
  • Why those invariants remain protected: change is purely renderer-side scroll behavior, no DB/IPC/protocol changes

Linked issues

Closes #

Validation

  • pnpm lint
  • pnpm test
  • pnpm check:ui-contract
  • pnpm check (full suite: lint + architecture + ui-contract + renderer-copy + i18n + dead-code + format + typecheck + test)
  • Manual smoke test

Commands, screenshots, or notes:

pnpm check — all gates passed (92+59+6 tests, zero failures)

Screenshots or recordings

No visual change. Scroll behavior is functionally identical — the fix eliminates forced reflow during streaming and resolves scroll context conflicts in virtualized mode.

Release note

  • No user-facing change. Release note is NONE.
NONE

Checklist

  • The PR title uses at least one approved prefix: [Feat], [Fix], [UI], [Docs], [Refactor], [Build], or [Chore]
  • The summary explains both what changed and why
  • Validation reflects the commands actually run for this PR
  • Architecture impact is described and references any touched invariants
  • Cross-layer changes are explicitly justified
  • The release note block is accurate

@github-actions
Copy link
Copy Markdown
Contributor

Hi @samzong,
Thanks for your pull request!
If the PR is ready, use the /auto-cc command to assign Reviewer to Review.
We will review it shortly.

Details

Instructions for interacting with me using comments are available here.
If you have questions or suggestions related to my behavior, please file an issue against the gh-ci-bot repository.

@samzong samzong merged commit b747d5f into main Mar 27, 2026
7 checks passed
@samzong samzong deleted the fix/chatview-scroll branch March 29, 2026 14:10
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.

1 participant