From 64f46ab281a2d49d42ef270d2f53b5f440799eb3 Mon Sep 17 00:00:00 2001 From: Dotta Date: Tue, 28 Apr 2026 16:13:36 -0500 Subject: [PATCH] Restore guarded initial issue scroll check Co-Authored-By: Paperclip --- ui/src/components/IssuesList.test.tsx | 6 ++++-- ui/src/components/IssuesList.tsx | 22 ++++++++++++++++------ 2 files changed, 20 insertions(+), 8 deletions(-) diff --git a/ui/src/components/IssuesList.test.tsx b/ui/src/components/IssuesList.test.tsx index a45658d2452..9ca81665943 100644 --- a/ui/src/components/IssuesList.test.tsx +++ b/ui/src/components/IssuesList.test.tsx @@ -987,7 +987,9 @@ describe("IssuesList", () => { expect(container.querySelectorAll('[data-testid="issue-row"]')).toHaveLength(100); }); await flush(); - expect(onLoadMoreIssues).not.toHaveBeenCalled(); + expect(onLoadMoreIssues).toHaveBeenCalledTimes(1); + await flush(); + expect(onLoadMoreIssues).toHaveBeenCalledTimes(1); act(() => { setDocumentScrollMetrics({ innerHeight: 600, scrollY: 1500, scrollHeight: 2000 }); @@ -995,7 +997,7 @@ describe("IssuesList", () => { }); await waitForAssertion(() => { - expect(onLoadMoreIssues).toHaveBeenCalledTimes(1); + expect(onLoadMoreIssues).toHaveBeenCalledTimes(2); }); act(() => { diff --git a/ui/src/components/IssuesList.tsx b/ui/src/components/IssuesList.tsx index 96995c467f1..9cf1a48d845 100644 --- a/ui/src/components/IssuesList.tsx +++ b/ui/src/components/IssuesList.tsx @@ -533,6 +533,7 @@ export function IssuesList({ const [renderedIssueRowLimit, setRenderedIssueRowLimit] = useState(INITIAL_ISSUE_ROW_RENDER_LIMIT); const [visibleIssueColumns, setVisibleIssueColumns] = useState(() => loadIssueColumns(scopedKey)); const renderedIssueIdsRef = useRef(""); + const initialServerFillRequestedRef = useRef(false); const deferredIssueSearch = useDeferredValue(issueSearch); const normalizedIssueSearch = deferredIssueSearch.trim().toLowerCase(); @@ -1033,31 +1034,40 @@ export function IssuesList({ const scrollContainer = findIssuesScrollContainer(rootRef.current); const scrollTarget: Window | HTMLElement = scrollContainer ?? window; - const checkScrollPosition = () => { + const checkScrollPosition = (trigger: "initial" | "scroll" | "resize" = "scroll") => { if (animationFrameId !== null) return; animationFrameId = window.requestAnimationFrame(() => { animationFrameId = null; const scrollHeight = scrollContainer?.scrollHeight ?? document.documentElement.scrollHeight; if (scrollHeight === 0) return; + const viewportHeight = scrollContainer?.clientHeight ?? window.innerHeight; const scrollBottom = scrollContainer ? scrollContainer.scrollTop + scrollContainer.clientHeight : window.scrollY + window.innerHeight; + const hasScrollableOverflow = scrollHeight > viewportHeight + 1; const threshold = scrollHeight - ISSUE_SCROLL_LOAD_THRESHOLD_PX; if (scrollBottom >= threshold) { + if (trigger === "initial" && !hasMoreRenderedRows && hasMoreIssues && !hasScrollableOverflow) { + if (initialServerFillRequestedRef.current) return; + initialServerFillRequestedRef.current = true; + } loadMoreIssueRows(); } }); }; - scrollTarget.addEventListener("scroll", checkScrollPosition, { passive: true }); - window.addEventListener("resize", checkScrollPosition); + const handleScroll = () => checkScrollPosition("scroll"); + const handleResize = () => checkScrollPosition("resize"); + scrollTarget.addEventListener("scroll", handleScroll, { passive: true }); + window.addEventListener("resize", handleResize); + checkScrollPosition("initial"); return () => { - scrollTarget.removeEventListener("scroll", checkScrollPosition); - window.removeEventListener("resize", checkScrollPosition); + scrollTarget.removeEventListener("scroll", handleScroll); + window.removeEventListener("resize", handleResize); if (animationFrameId !== null) window.cancelAnimationFrame(animationFrameId); }; - }, [canLoadMoreIssues, loadMoreIssueRows]); + }, [canLoadMoreIssues, hasMoreIssues, hasMoreRenderedRows, loadMoreIssueRows]); const newIssueDefaults = useCallback((groupKey?: string) => { const defaults: Record = { ...(baseCreateIssueDefaults ?? {}) };