Skip to content

Comments

fix(acp-client): fix auto-scroll bugs on replay, clear, and streaming#170

Merged
raphaeltm merged 2 commits intomainfrom
fix/auto-scroll-bugs
Feb 23, 2026
Merged

fix(acp-client): fix auto-scroll bugs on replay, clear, and streaming#170
raphaeltm merged 2 commits intomainfrom
fix/auto-scroll-bugs

Conversation

@raphaeltm
Copy link
Owner

@raphaeltm raphaeltm commented Feb 23, 2026

Summary

  • Fix scrolled-to-top on refresh/replay: Added resetToBottom() to useAutoScroll hook and wired it into AgentPanel to reset stale scroll tracking when items are cleared (replay) or session finishes replaying
  • Fix bouncing near bottom during streaming: RAF callback now re-checks actual scroll geometry at execution time instead of relying solely on isAtBottomRef, which can go stale from browser layout shifts between observer callbacks
  • Safety net for replay complete: Added useEffect that scrolls to bottom when session transitions from replaying to ready/prompting

Root cause

All three bugs stemmed from isAtBottomRef going stale between observer callbacks and DOM updates. prepareForReplay() clears items (async React state), but the ref retains the user's old scroll position, causing subsequent messages to not auto-scroll.

Changes

  • useAutoScroll.ts: Added resetToBottom(), fixed RAF to use isAtBottomRef.current || checkIsAtBottom(el)
  • AgentPanel.tsx: Two new useEffect hooks for replay clear and replay complete
  • useAutoScroll.test.ts: 4 new tests (resetToBottom, RAF geometry re-check)
  • AgentPanel.test.tsx: 5 new tests (replay clear, replay complete, no false positives)

Validation

  • pnpm lint
  • pnpm typecheck
  • pnpm test
  • Additional validation run (if applicable)
  • Mobile and desktop verification notes added for UI changes

UI Compliance Checklist (Required for UI changes)

  • Mobile-first layout verified — no layout changes, scroll behavior only
  • Accessibility checks completed — N/A, no UI elements added
  • Shared UI components used or exception documented — uses existing useAutoScroll hook

Exceptions (If any)

  • Scope: N/A
  • Rationale: N/A
  • Expiration: N/A

Agent Preflight (Required)

  • Preflight completed before code changes

Classification

  • external-api-change
  • cross-component-change
  • business-logic-change
  • public-surface-change
  • docs-sync-change
  • security-sensitive-change
  • ui-change
  • infra-change

External References

N/A: Bug fix to internal scroll behavior hook. No external APIs or libraries involved beyond standard browser APIs (ResizeObserver, MutationObserver, requestAnimationFrame).

Codebase Impact Analysis

  • packages/acp-client/src/hooks/useAutoScroll.ts — Added resetToBottom() to return value; fixed RAF callback to re-check geometry
  • packages/acp-client/src/components/AgentPanel.tsx — Two new useEffect hooks wiring resetToBottom to replay lifecycle events
  • packages/acp-client/src/hooks/useAutoScroll.test.ts — 4 new tests
  • packages/acp-client/src/components/AgentPanel.test.tsx — 5 new tests

Documentation & Specs

N/A: Internal bug fix to scroll behavior. No public API changes, no spec changes, no documentation references to scroll behavior.

Constitution & Risk Check

  • Principle XI (No Hardcoded Values): bottomThreshold is configurable via UseAutoScrollOptions. No new hardcoded values introduced.
  • Risk: Minimal — changes are additive (new return value, new useEffects). The RAF geometry re-check is strictly more permissive (scrolls when ref OR geometry says at bottom), which is the correct behavior for streaming content.

Generated with Claude Code

raphaeltm and others added 2 commits February 23, 2026 16:07
Three related bugs all stemmed from stale isAtBottomRef state:

1. Scrolled to top on refresh/replay — prepareForReplay() clears items
   but isAtBottomRef stays false from previous scroll position, so new
   replay messages don't auto-scroll.

2. Bouncing near bottom — ResizeObserver and MutationObserver fire
   between RAF callbacks, scroll events briefly set isAtBottom=false,
   next observer skips auto-scroll.

3. Lost scroll on tool call expansion — same stale-ref mechanism.

Fixes:
- Add resetToBottom() to useAutoScroll return value for clearing stale
  scroll state after replay/clear
- Fix RAF callback to re-check actual scroll geometry at execution time
  instead of relying solely on potentially stale isAtBottomRef
- Add useEffect in AgentPanel to call resetToBottom when items go from
  >0 to 0 (replay clear signal)
- Add useEffect in AgentPanel to call resetToBottom when session
  transitions from replaying to ready/prompting (replay complete)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@raphaeltm raphaeltm merged commit 7a6d64f into main Feb 23, 2026
16 checks passed
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