From 4937cba1eb78ae8b546d207b36cc8862aa2fa731 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 25 Dec 2025 09:06:05 +0000 Subject: [PATCH 1/6] fix: Default hasMoreResults to true while loading The infinite scroll trigger was broken because hasMoreResults returned undefined (falsy) when SWR data hadn't loaded yet. This prevented the InfiniteScroll component from triggering the next fetch. Now hasMoreResults defaults to true while isFetchingClimbs is true (so infinite scroll works during SSR hydration), and false after loading completes with no data. --- .../queue-control/hooks/use-queue-data-fetching.tsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/web/app/components/queue-control/hooks/use-queue-data-fetching.tsx b/packages/web/app/components/queue-control/hooks/use-queue-data-fetching.tsx index 568053c0..6fbc97f6 100644 --- a/packages/web/app/components/queue-control/hooks/use-queue-data-fetching.tsx +++ b/packages/web/app/components/queue-control/hooks/use-queue-data-fetching.tsx @@ -61,7 +61,9 @@ export const useQueueDataFetching = ({ initialSize: searchParams.page ? searchParams.page + 1 : 1, }); - const hasMoreResults = data && data[0] && size * PAGE_LIMIT < data[0].totalCount; + // When data exists, calculate normally. Otherwise default to true while loading + // (so infinite scroll works during SSR hydration), or false after loading completes with no data + const hasMoreResults = data?.[0] ? size * PAGE_LIMIT < data[0].totalCount : isFetchingClimbs; const totalSearchResultCount = (data && data[0] && data[0].totalCount) || null; const climbSearchResults = useMemo( From 4698b4b764b452457f87ef0c1110d84edc8bb330 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 25 Dec 2025 09:21:31 +0000 Subject: [PATCH 2/6] fix: Remove overflow-x from .ant-layout-content CSS The CSS rule setting overflow-x: hidden on .ant-layout-content was interfering with the scroll detection for react-infinite-scroll-component. The scroll container (#content-for-scrollable) has its overflow properties set via inline styles, which should be the source of truth. Removing this CSS rule allows the InfiniteScroll component to properly detect scroll events and trigger loading more climbs. --- packages/web/app/components/index.css | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/web/app/components/index.css b/packages/web/app/components/index.css index 173506a9..dc94f63f 100644 --- a/packages/web/app/components/index.css +++ b/packages/web/app/components/index.css @@ -19,12 +19,14 @@ html, body { } /* Prevent horizontal scroll on layout components */ -.ant-layout, -.ant-layout-content { +.ant-layout { max-width: 100%; overflow-x: hidden; } +/* Note: .ant-layout-content overflow is controlled by inline styles on #content-for-scrollable + to enable proper infinite scroll detection. Do not set overflow here. */ + /* Prevent double-tap zoom globally while preserving pinch-to-zoom */ html { touch-action: manipulation; From a1027ae7cfa8e1f46730f265a26ad9feeb2121df Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 25 Dec 2025 09:37:06 +0000 Subject: [PATCH 3/6] fix: Auto-trigger infinite scroll when content doesn't fill container Add useEffect to manually trigger fetchMoreClimbs when the initial content doesn't fill the scroll container. This handles the case where react-infinite-scroll-component's auto-detection doesn't work properly because there's no scrollbar to trigger scroll events. --- .../web/app/components/board-page/climbs-list.tsx | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/packages/web/app/components/board-page/climbs-list.tsx b/packages/web/app/components/board-page/climbs-list.tsx index af790f53..7e44134c 100644 --- a/packages/web/app/components/board-page/climbs-list.tsx +++ b/packages/web/app/components/board-page/climbs-list.tsx @@ -126,6 +126,21 @@ const ClimbsList = ({ boardDetails, initialClimbs }: ClimbsListProps) => { } }, [page, hasDoneFirstFetch, isFetchingClimbs]); // Depend on the page query parameter + // Auto-trigger load when content doesn't fill the scroll container + // This handles the case where initial climbs don't fill the viewport + useEffect(() => { + if (!hasMoreResults || isFetchingClimbs) return; + + const scrollContainer = document.getElementById('content-for-scrollable'); + if (!scrollContainer) return; + + // Check if content doesn't fill the container (no scrollbar needed) + const needsMoreContent = scrollContainer.scrollHeight <= scrollContainer.clientHeight; + if (needsMoreContent) { + fetchMoreClimbs(); + } + }, [hasMoreResults, isFetchingClimbs, climbs.length, fetchMoreClimbs]); + return ( Date: Thu, 25 Dec 2025 10:08:40 +0000 Subject: [PATCH 4/6] fix: Remove auto-trigger to debug scroll behavior --- .../web/app/components/board-page/climbs-list.tsx | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/packages/web/app/components/board-page/climbs-list.tsx b/packages/web/app/components/board-page/climbs-list.tsx index 7e44134c..af790f53 100644 --- a/packages/web/app/components/board-page/climbs-list.tsx +++ b/packages/web/app/components/board-page/climbs-list.tsx @@ -126,21 +126,6 @@ const ClimbsList = ({ boardDetails, initialClimbs }: ClimbsListProps) => { } }, [page, hasDoneFirstFetch, isFetchingClimbs]); // Depend on the page query parameter - // Auto-trigger load when content doesn't fill the scroll container - // This handles the case where initial climbs don't fill the viewport - useEffect(() => { - if (!hasMoreResults || isFetchingClimbs) return; - - const scrollContainer = document.getElementById('content-for-scrollable'); - if (!scrollContainer) return; - - // Check if content doesn't fill the container (no scrollbar needed) - const needsMoreContent = scrollContainer.scrollHeight <= scrollContainer.clientHeight; - if (needsMoreContent) { - fetchMoreClimbs(); - } - }, [hasMoreResults, isFetchingClimbs, climbs.length, fetchMoreClimbs]); - return ( Date: Thu, 25 Dec 2025 10:09:48 +0000 Subject: [PATCH 5/6] fix: Auto-trigger infinite scroll when content doesn't fill container Uses a ref to track when fetching completes, then checks if the scroll container needs more content. This avoids timeouts and only triggers after each fetch completes or on initial mount. --- .../app/components/board-page/climbs-list.tsx | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/packages/web/app/components/board-page/climbs-list.tsx b/packages/web/app/components/board-page/climbs-list.tsx index af790f53..3a35484d 100644 --- a/packages/web/app/components/board-page/climbs-list.tsx +++ b/packages/web/app/components/board-page/climbs-list.tsx @@ -126,6 +126,30 @@ const ClimbsList = ({ boardDetails, initialClimbs }: ClimbsListProps) => { } }, [page, hasDoneFirstFetch, isFetchingClimbs]); // Depend on the page query parameter + // Auto-trigger load when content doesn't fill the scroll container + const wasFetchingRef = useRef(isFetchingClimbs); + + useEffect(() => { + const wasFetching = wasFetchingRef.current; + wasFetchingRef.current = isFetchingClimbs; + + // Only check when fetch just completed (was fetching, now not) + // OR on initial mount when not fetching + const fetchJustCompleted = wasFetching && !isFetchingClimbs; + const isInitialCheck = !wasFetching && !isFetchingClimbs && climbs.length > 0; + + if (!hasMoreResults || isFetchingClimbs) return; + if (!fetchJustCompleted && !isInitialCheck) return; + + const scrollContainer = document.getElementById('content-for-scrollable'); + if (!scrollContainer) return; + + const needsMoreContent = scrollContainer.scrollHeight <= scrollContainer.clientHeight; + if (needsMoreContent) { + fetchMoreClimbs(); + } + }, [hasMoreResults, isFetchingClimbs, climbs.length, fetchMoreClimbs]); + return ( Date: Thu, 25 Dec 2025 10:29:32 +0000 Subject: [PATCH 6/6] fix: Ensure hasMoreResults is always a boolean with proper null checks --- .../queue-control/hooks/use-queue-data-fetching.tsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/web/app/components/queue-control/hooks/use-queue-data-fetching.tsx b/packages/web/app/components/queue-control/hooks/use-queue-data-fetching.tsx index 6fbc97f6..20504288 100644 --- a/packages/web/app/components/queue-control/hooks/use-queue-data-fetching.tsx +++ b/packages/web/app/components/queue-control/hooks/use-queue-data-fetching.tsx @@ -63,7 +63,9 @@ export const useQueueDataFetching = ({ // When data exists, calculate normally. Otherwise default to true while loading // (so infinite scroll works during SSR hydration), or false after loading completes with no data - const hasMoreResults = data?.[0] ? size * PAGE_LIMIT < data[0].totalCount : isFetchingClimbs; + const hasMoreResults = data?.[0]?.totalCount !== undefined + ? size * PAGE_LIMIT < data[0].totalCount + : Boolean(isFetchingClimbs); const totalSearchResultCount = (data && data[0] && data[0].totalCount) || null; const climbSearchResults = useMemo(