fix(web): preserve Agents panel scroll position - #9637
Conversation
| import { ScrollArea } from "~/components/ui/scroll-area"; | ||
| import { Button } from "~/components/ui/button"; | ||
|
|
||
| const agentsScrollTopByThreadKey = new Map<string, number>(); |
There was a problem hiding this comment.
🟡 Medium components/AgentsPanel.tsx:33
agentsScrollTopByThreadKey grows for the lifetime of the renderer, so creating and deleting many agent threads retains every obsolete thread key and causes unbounded memory growth. Add deletion when a thread is removed (or otherwise bound/evict this cache) so entries do not accumulate indefinitely.
🤖 Copy this AI Prompt to have your agent fix this:
In file @apps/web/src/components/AgentsPanel.tsx around line 33:
`agentsScrollTopByThreadKey` grows for the lifetime of the renderer, so creating and deleting many agent threads retains every obsolete thread key and causes unbounded memory growth. Add deletion when a thread is removed (or otherwise bound/evict this cache) so entries do not accumulate indefinitely.
There was a problem hiding this comment.
Keeping this as designed. The cache is renderer-session-only and stores one number per thread whose Agents panel was visited; it resets on reload. Wiring thread deletion or adding eviction would expand the feature and could discard scroll state the user expects to recover.
There was a problem hiding this comment.
Sorry, I'm unable to act on this request because you do not have permissions within this repository.
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes using default effort and found 1 potential issue.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Want higher recall? High effort reviews run extra passes and find more bugs. A team admin can switch effort levels in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 48b7360. Configure here.
ApprovabilityVerdict: Would Approve Macroscope's review found this PR approvable — This is a focused UI bug fix that preserves Agents-panel scroll offsets with small, thread-scoped runtime changes and comprehensive focused tests; the shared ScrollArea change is optional and leaves existing callers unchanged. An unresolved Medium finding about unbounded renderer-session cache growth remains an independent correctness risk. Not approved because:
Adjust the Minimum Blocking Severity for this repo — including turning it Off — in Settings. You can add or adjust custom eligibility rules. Learn more. |
59ac619 to
a5a25fa
Compare
Bugbot is paused — on-demand spend limit reachedBugbot uses usage-based billing for this team and has hit its on-demand spend limit. A team admin can raise the spend limit in the Cursor dashboard, or wait for the next billing cycle to continue. |
a5a25fa to
d633b9e
Compare
Bugbot is paused — on-demand spend limit reachedBugbot uses usage-based billing for this team and has hit its on-demand spend limit. A team admin can raise the spend limit in the Cursor dashboard, or wait for the next billing cycle to continue. |
ad86810 to
e079fee
Compare
Remember each thread's Agents viewport offset for the lifetime of the renderer so switching threads, tabs, visibility, or panel layouts no longer returns users to the top. Keep thread lifetimes isolated by the existing environment-qualified thread key and avoid React updates while scrolling. Verification: 8 focused tests, web typecheck, targeted lint and formatting.
Keep the saved Agents offset when a remounted layout has a smaller scroll range and the browser clamps the viewport. This lets the original layout restore its deeper position instead of inheriting the temporary maximum. Verification: 9 focused tests, web typecheck, targeted lint and formatting.
Treat viewport positions within one pixel of the calculated scroll maximum as clamped so browser rounding cannot replace a deeper saved Agents offset. Verification: 9 focused tests, web typecheck, targeted lint and formatting.
e079fee to
72f4d0f
Compare

What Changed
When the Agents panel remounted after switching threads, changing right-panel tabs, hiding the panel, or moving between inline and sheet layouts, it returned to the top.
Remember each thread's Agents viewport offset in renderer memory, keyed by its environment-qualified thread key. Restore the offset before paint when the viewport remounts, and give each thread a distinct
AgentsPanellifetime so view state cannot leak between threads.ScrollAreanow exposes its real scrolling viewport through an optional ref while preserving its existing root ref.Why
Agent rosters can grow long, and navigating away from a thread should not lose the user's place. Renderer-only memory preserves that context across panel remounts without adding persisted client state, server state, or wire changes. Scroll capture uses a passive listener and does not trigger React renders. If a remounted layout has a smaller scroll range, its clamped position does not overwrite the deeper saved offset.
UI Changes
This changes interaction behavior without changing the panel's visual design.
CleanShot.2026-09-04.at.05.33.04.mp4
Validation
Nine focused tests cover per-thread and per-environment isolation, tab and visibility remounts, inline/sheet remounts, delayed roster appearance, viewport ref behavior, and scroll capture without rerenders. Web typecheck, targeted lint, formatting, and
git diff --checkpass. The behavior was also tested manually in an isolated local web client using cloned data; no live user state was changed.Web and desktop are covered by the shared web surface. Mobile has no Agents panel.
Checklist
Created with GPT-5.6 Sol in Codex.
Note
Preserve Agents panel scroll position across thread switches and remounts
AgentsPanelnow stores per-thread vertical scroll offsets in a module-level map keyed bythreadKey, restoring the savedscrollTopwhen the roster viewport mounts and capturing scroll events without React state updatesScrollAreagains an optionalviewportRefprop so callers can attach a ref to the scrolling viewport elementChatViewpassesactiveThreadKeytoAgentsPanelvia the newthreadKeyprop and uses it as the React key, causing a remount on thread changeAgentsPanelnow requires a non-nullthreadKeyprop; callers omitting it will get no scroll persistenceMacroscope summarized 72f4d0f.
Note
Low Risk
UI-only scroll persistence in renderer memory; ScrollArea adds an optional prop without changing default behavior.
Overview
Fixes the Agents right panel jumping back to the top when you switch threads, change tabs, hide the panel, or remount the layout.
AgentsPanel now keeps scroll offsets in an in-memory map keyed by
threadKey. On mount it restoresscrollTopviauseLayoutEffect, updates the map with a passive scroll listener (no React state), and avoids overwriting a deeper saved offset when a remounted viewport is temporarily shorter. ChatView keys the panel byactiveThreadKeyand passes that asthreadKey.ScrollArea gains an optional
viewportRefso the panel can target the real scrolling element; root ref behavior is unchanged.Adds a lightweight
reactTestDomhelper plus tests for per-thread/environment isolation, remount scenarios, viewport ref wiring, and scroll capture without extra renders.Reviewed by Cursor Bugbot for commit d547124. Bugbot is set up for automated code reviews on this repo. Configure here.