fix(644): page SPA list loaders to completeness instead of inflating pageSize

Seven call sites (rerun-collections, multi-collections, library/browse) requested
pageSize far above each endpoint's server-side MaxPageSize=100 clamp and took the
single response page as the whole list, so rows past 100 silently vanished with no
error or truncation indicator.

Extract the loadAllRerunCollections pattern from SchedulesScreen (#634) into a
shared, generic web/src/api/paging.ts::loadAllPages helper that pages against
totalCount with an empty-page defensive break, and refactor SchedulesScreen plus
the seven over-cap call sites in RerunCollectionsScreen, MultiCollectionsScreen,
PlaylistsScreen, and FillerPresetsScreen to use it. Server caps are unchanged
(api.search-allitems-paging precedent: client pages, server stays bounded).

Document the convention in docs/spa-conventions.md §3b.
This commit is contained in:
2026-07-26 20:32:06 +02:00
parent 34591c3ef6
commit fe342a6a0b
9 changed files with 295 additions and 43 deletions
+28
View File
@@ -121,6 +121,34 @@ Convention — when a screen keeps stale results visible during a refetch:
current (compare against a ref that always holds the committed value — `SearchScreen` reuses
`lastQueryRef`) and **discard** otherwise. Checking only `activeRef` (mounted) is insufficient.
## 3b. Paged list endpoints clamp server-side — page to completeness, don't inflate `pageSize`
Every paged `/api/v1` list endpoint (rerun-collections, multi-collections, library/browse, search,
trakt-lists, …) clamps `pageSize` to its own controller's `MaxPageSize` (100, as of #644) regardless
of what the client requests. A screen that asks for `pageSize: 1000` to "get everything in one call"
gets only the first `MaxPageSize` rows back, silently — no error, no truncation indicator, no paging
UI to notice the gap. This was issue #644 (following on from #634, which fixed the first instance —
`SchedulesScreen`'s rerun-collections picker load).
**If a screen genuinely needs the complete list** (not a paginated view — e.g. a picker/typeahead
data source), use the shared `loadAllPages` helper (`web/src/api/paging.ts`, re-exported via
`web/src/api/index.ts`) instead of an inflated `pageSize`:
```ts
loadAllPages(getMultiCollections) // pages against totalCount, cap defaults to 100
loadAllPages(getLibraryBrowseItems, { mediaType: 'Movie' }) // extra fixed params thread into every page
```
It pages `pageNum` from 0 (per §"paging-zero-based" in `api-conventions.md`) against the response's
`totalCount`, breaking early on an empty page as a defensive guard against a `totalCount` that never
converges. **Do not raise the server-side cap to work around this** — the `api.search-allitems-paging`
precedent is that the client pages and the server stays bounded; that's a backend decision, out of
scope for a screen fix.
**If a screen shows a bounded preview or has real paging UI** (a "load more" button, a page-size
selector, a fixed-size typeahead result list), a `pageSize` at or below the cap is correct as-is —
`loadAllPages` is only for "I need literally everything" call sites.
## 4. API client modules
One file per domain in `web/src/api/`, e.g. `logs.ts`, `blocks.ts`, `playouts.ts`. Pattern (see