diff --git a/packages/views/agents/components/agents-page.tsx b/packages/views/agents/components/agents-page.tsx index 2d86dbb53..3f3b06f3b 100644 --- a/packages/views/agents/components/agents-page.tsx +++ b/packages/views/agents/components/agents-page.tsx @@ -934,12 +934,18 @@ export function AgentsPage(_props: AgentsPageProps = {}) { return filtered; }, [scopeRows, filters, sortField, sortDirection]); - // Row virtualization — headless math, offsets as padding on the body, - // fixed-height rows. - const listBodyRef = useRef(null); + // Row virtualization — headless math, offsets as padding on the rows + // wrapper, fixed-height rows. The scroll element is the SINGLE outer + // scroller (both axes): splitting horizontal scrolling (wrapper) from + // vertical scrolling (an inner element) connected by an h-full + // percentage bridge caused a non-converging layout loop (flickering + // double scrollbars) and clipped the last row under the horizontal + // scrollbar. The sticky header pins inside this scroller; the vertical + // scrollbar spans the full pane height (Linear's structure). + const listScrollRef = useRef(null); const rowVirtualizer = useVirtualizer({ count: rows.length, - getScrollElement: () => listBodyRef.current, + getScrollElement: () => listScrollRef.current, estimateSize: () => ROW_HEIGHT, overscan: 10, }); @@ -1031,9 +1037,12 @@ export function AgentsPage(_props: AgentsPageProps = {}) { allRows={scopeRows} visibleCount={rows.length} /> -
+
+ {/* Plain subgrid passthrough (NOT ListGridBody): scrolling + lives on the outer wrapper, this div only groups the rows + and carries the virtualization padding. */}