fix(frontend): make infinite scroll viewport-relative, stop eager-loading

Lazy load fetched the entire list at once: every list's loader had a
"fill the viewport" recursion gated on
scrollContainer.scrollHeight <= clientHeight, but <main> is not the
scroller (the window/body is), so that condition is always true and it
recursed through every page (with a 10-item window, ~all pages fired at
once).

Move the filling logic into InfiniteScroll and base it on the sentinel's
viewport rect instead: load while the sentinel is within 300px of the
viewport bottom, re-checked synchronously after each load. This works
regardless of which element scrolls and loads only enough pages to reach
past the viewport.

Drop the per-page recursion (and now-unused scrollContainer refs / tick
imports) from the files, trash, tags, categories and pools lists.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-11 00:14:04 +03:00
parent ffb8848a96
commit dc1af8c585
6 changed files with 35 additions and 48 deletions
+3 -6
View File
@@ -203,12 +203,9 @@
} finally {
loading = false;
}
// If the loaded content doesn't fill the viewport yet (no scrollbar),
// keep loading until it does or there's nothing left.
await tick();
if (hasMore && scrollContainer && scrollContainer.scrollHeight <= scrollContainer.clientHeight) {
void loadMore();
}
// Viewport filling is handled by InfiniteScroll, which re-checks after each
// load — no manual recursion (which over-fetched here because <main> isn't
// the scroller, so its scrollHeight never exceeds its clientHeight).
}
function applyFilter(filter: string | null) {
+1 -8
View File
@@ -1,7 +1,6 @@
<script lang="ts">
import { goto } from '$app/navigation';
import { api, ApiError } from '$lib/api/client';
import { tick } from 'svelte';
import FileCard from '$lib/components/file/FileCard.svelte';
import InfiniteScroll from '$lib/components/common/InfiniteScroll.svelte';
import ConfirmDialog from '$lib/components/common/ConfirmDialog.svelte';
@@ -9,8 +8,6 @@
import { appSettings } from '$lib/stores/appSettings';
import type { File, FileCursorPage } from '$lib/api/types';
let scrollContainer = $state<HTMLElement | undefined>();
let LIMIT = $derived($appSettings.fileLoadLimit);
let files = $state<File[]>([]);
@@ -47,10 +44,6 @@
loading = false;
initialLoaded = true;
}
await tick();
if (hasMore && scrollContainer && scrollContainer.scrollHeight <= scrollContainer.clientHeight) {
void loadMore();
}
}
// ---- Selection ----
@@ -165,7 +158,7 @@
</button>
</header>
<main bind:this={scrollContainer}>
<main>
{#if error}
<p class="error" role="alert">{error}</p>
{/if}