[Library] Show all results (not just paginated) #1

Closed
opened 2026-08-23 11:04:14 +02:00 by vmruiz · 1 comment
Owner

Current behaviour

The Library page (app/main.py:455, route /) paginates results with a fixed page size. LIBRARY_PAGE_SIZE = 50 (app/main.py:219) and the response only returns the current page via offset/limit (app/main.py:497-498). There is no way to view all matching results at once; you have to navigate page by page (library.html:210-213 uses total_pages and page).

Desired behaviour

Be able to show all matching results in a single view without manual pagination. The UI should allow changing the page size (e.g. include an "All" option alongside the numeric sizes), keeping filters, sorting and the selection bar working as before.

Goals

  • Add a page-size control to the Library filter panel that includes an "All" option in addition to the current values.
  • When "All" is selected, the backend must not apply offset/limit (or use a sufficiently large limit) and must return every result matching the current filters.
  • Persist the chosen page size in the URL / query params (library.html already uses query params via _library_url).
  • Keep start_index/end_index/total correct, keep numeric pagination when a numeric size is chosen, and keep the selected ordering.
  • Follow the current design system (UI in app/static/app.css, vanilla JS, no build step per AGENTS.md).
## Current behaviour The Library page (`app/main.py:455`, route `/`) paginates results with a fixed page size. `LIBRARY_PAGE_SIZE = 50` (`app/main.py:219`) and the response only returns the current page via `offset`/`limit` (`app/main.py:497-498`). There is no way to view all matching results at once; you have to navigate page by page (`library.html:210-213` uses `total_pages` and `page`). ## Desired behaviour Be able to show all matching results in a single view without manual pagination. The UI should allow changing the page size (e.g. include an "All" option alongside the numeric sizes), keeping filters, sorting and the selection bar working as before. ## Goals - Add a page-size control to the Library filter panel that includes an "All" option in addition to the current values. - When "All" is selected, the backend must not apply `offset`/`limit` (or use a sufficiently large limit) and must return every result matching the current filters. - Persist the chosen page size in the URL / query params (`library.html` already uses query params via `_library_url`). - Keep `start_index`/`end_index`/`total` correct, keep numeric pagination when a numeric size is chosen, and keep the selected ordering. - Follow the current design system (UI in `app/static/app.css`, vanilla JS, no build step per `AGENTS.md`).
vmruiz changed title from [Library] Mostrar todos los resultados (no solo paginados) to [Library] Show all results (not just paginated) 2026-08-23 11:04:42 +02:00
Author
Owner

Proposal — lazy-loading infinite scroll

Picking this one up. Plan below; will start once a maintainer signals go.

Approach

Replace the fixed 50-item pagination with an IntersectionObserver-driven infinite scroll:

  • Initial GET / still server-renders the first page (SSR + no-JS keeps working).
  • New JSON endpoint GET /items/page?offset=&limit=&<filter params>&<sort> returns {items, total, has_more}.
  • A sentinel <div data-infinite-scroll-sentinel> at the bottom of the grid triggers a fetch; cards append via a renderCard(item) helper extracted from the current template.
  • Filter / sort changes reset to page 1 (already true today).
  • Server caps limit at MAX_PAGE_LIMIT = 100 so a buggy client cannot pull 10k rows.

Files touched

  • app/main.py — refactor filter → query into a single helper used by both the SSR route and the new GET /items/page. Add MAX_PAGE_LIMIT.
  • app/templates/library.html — sentinel + skeleton + end-of-results marker.
  • app/static/app.js — infinite-scroll module: IntersectionObserver, fetch next page, dedupe guard, error toast, selection-toolbar integration, auto-refresh pause while a fetch is pending.
  • app/static/app.css — only if a new state (loading / end / error inline notice) needs a rule; otherwise reuse existing .skeleton / .notice / .toast-region per AGENTS.md.
  • tests/ — new tests for the new endpoint (slice, has_more, cap, filter round-trip) + a regression test that fails on the old "fixed page only" behaviour.

Acceptance

  1. Initial page renders the first slice (e.g. 50).
  2. Scrolling near the bottom fetches the next slice and appends cards; no flicker, no dupes.
  3. URL keeps filters + sort. Page position is not in the URL (infinite scroll is not a paginated URL state).
  4. Sentinel + IntersectionObserver + visible loading state + end-of-results marker.
  5. New endpoint GET /items/page with offset, limit, filter, sort → {items, total, has_more}.
  6. Filter / sort changes reset to page 1.
  7. Empty, end-of-results, network error states all visible to the user.
  8. Selection toolbar keeps working during scroll; auto-refresh pause invariant preserved.
  9. MAX_PAGE_LIMIT = 100 server cap.
  10. No regression to current pagination metadata (total, start_index, end_index).

Risks

  • Auto-refresh + scroll-fetch race → pause auto-refresh while a fetch is pending (mirrors the existing selection-pause invariant).
  • Selection state across scrolls → reuse the existing event delegation on the grid container; do not attach per-card listeners.
  • Open remote branches fix/library-filtering and feat/thumbnail-recovery-jobs → rebase/merge against current main first; if fix/library-filtering overlaps, work on top.

Out of scope (tracked separately)

  • Issue #2 ("Select all results, not just those on page 1") — infinite scroll removes the page-1 vs page-N distinction naturally; selection bar keeps a running set as cards stream in. We can revisit if you still want a "Select all N matching" action.
  • Seed data — I am opening a separate issue for python -m app.cli seed-demo so this PR stays focused on the UI change and the test environment can be populated independently.

Deploy

Feature branch → existing Forgejo workflow builds docker.celor.es/telegramarr:sha-<short> → side-by-side compose on the runner host pulls the sha-tagged image on a different port for manual testing. Production :latest stays untouched until you green-light the merge.

## Proposal — lazy-loading infinite scroll Picking this one up. Plan below; will start once a maintainer signals go. ### Approach Replace the fixed 50-item pagination with an **IntersectionObserver-driven infinite scroll**: - Initial GET `/` still server-renders the first page (SSR + no-JS keeps working). - New JSON endpoint `GET /items/page?offset=&limit=&<filter params>&<sort>` returns `{items, total, has_more}`. - A sentinel `<div data-infinite-scroll-sentinel>` at the bottom of the grid triggers a fetch; cards append via a `renderCard(item)` helper extracted from the current template. - Filter / sort changes reset to page 1 (already true today). - Server caps `limit` at `MAX_PAGE_LIMIT = 100` so a buggy client cannot pull 10k rows. ### Files touched - `app/main.py` — refactor filter → query into a single helper used by both the SSR route and the new `GET /items/page`. Add `MAX_PAGE_LIMIT`. - `app/templates/library.html` — sentinel + skeleton + end-of-results marker. - `app/static/app.js` — infinite-scroll module: IntersectionObserver, fetch next page, dedupe guard, error toast, selection-toolbar integration, auto-refresh pause while a fetch is pending. - `app/static/app.css` — only if a new state (loading / end / error inline notice) needs a rule; otherwise reuse existing `.skeleton` / `.notice` / `.toast-region` per `AGENTS.md`. - `tests/` — new tests for the new endpoint (slice, `has_more`, cap, filter round-trip) + a regression test that fails on the old "fixed page only" behaviour. ### Acceptance 1. Initial page renders the first slice (e.g. 50). 2. Scrolling near the bottom fetches the next slice and appends cards; no flicker, no dupes. 3. URL keeps filters + sort. **Page position is not in the URL** (infinite scroll is not a paginated URL state). 4. Sentinel + IntersectionObserver + visible loading state + end-of-results marker. 5. New endpoint `GET /items/page` with `offset`, `limit`, filter, sort → `{items, total, has_more}`. 6. Filter / sort changes reset to page 1. 7. Empty, end-of-results, network error states all visible to the user. 8. Selection toolbar keeps working during scroll; auto-refresh pause invariant preserved. 9. `MAX_PAGE_LIMIT = 100` server cap. 10. No regression to current pagination metadata (`total`, `start_index`, `end_index`). ### Risks - Auto-refresh + scroll-fetch race → pause auto-refresh while a fetch is pending (mirrors the existing selection-pause invariant). - Selection state across scrolls → reuse the existing event delegation on the grid container; do not attach per-card listeners. - Open remote branches `fix/library-filtering` and `feat/thumbnail-recovery-jobs` → rebase/merge against current `main` first; if `fix/library-filtering` overlaps, work on top. ### Out of scope (tracked separately) - Issue #2 ("Select all results, not just those on page 1") — infinite scroll removes the page-1 vs page-N distinction naturally; selection bar keeps a running set as cards stream in. We can revisit if you still want a "Select all N matching" action. - Seed data — I am opening a separate issue for `python -m app.cli seed-demo` so this PR stays focused on the UI change and the test environment can be populated independently. ### Deploy Feature branch → existing Forgejo workflow builds `docker.celor.es/telegramarr:sha-<short>` → side-by-side compose on the runner host pulls the sha-tagged image on a different port for manual testing. Production `:latest` stays untouched until you green-light the merge.
Sign in to join this conversation.
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
vmruiz/telegramarr#1
No description provided.