[fix] Release the scroll guard when a transcript node swap cancels a pin - #5605
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
Important Review skippedAuto reviews are disabled on base/target branches other than the default branch. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Plus Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Railway Preview Environment
Updated at 2026-07-31T19:18:23.555Z |
5f2d450
into
fe-chore/agent-conversation-split
Fix for a finding on #5569. Targets
fe-chore/agent-conversation-splitso it lands as part of that PR.Context
When the plain transcript container is replaced while a pin animation is still running, the chat stops following the streaming answer and message anchoring stops compensating for content that grows above the viewport. It stays broken until some later pin happens to finish on its own.
animatePinToholdsprogrammaticScrollReftrue for the length of the glide, so the scroll handler and the resize observer ignore the in-between frames. It releases the guard inside the settle callback. The cancel path stored onpinCleanupRefremoves the listeners and clears the fallback timer, but it never touches the guard, and cancelling skips settle by design. The node-swap layout effect calls that cancel path, so the guard leaks astrue.It leaks past the remount because
programmaticScrollRefcomes fromintent, which the parent owns. The ref outlives this hook, so the stuck value is still there when the plain engine mounts again.Three consumers bail out while the guard is set: the follow-to-bottom effect, the resize anchoring effect, and the scroll handler's anchoring branch.
The container is conditionally rendered (
AgentTranscript.tsx:43and:101), so it swaps whenever virtualization takes over from an empty conversation, and whenever the transcript remounts within a session.Changes
Release the guard at the two places that cancel a pin for teardown: the node-swap layout effect and the unmount cleanup.
The guard is deliberately not released inside the shared cancel closure.
animatePinTocalls that same closure to supersede a previous pin, and every caller sets the guard totrueimmediately before calling it, so clearing there would wipe the guard that was just set.Ordering is safe. The node-swap effect is declared before the SC-1/SC-2 pin effect, and React runs layout effects in declaration order, so a pin that needs the guard sets it after this clears it.
Notes
I could not run
pnpm lint-fix, because the checkout I used has nonode_modules. The change is two statements plus a comment and follows the surrounding style.What to QA