Library: infinite-scroll the results grid (refs #1) #8

Merged
vmruiz merged 6 commits from fix/library-infinite-scroll into main 2026-08-23 18:47:40 +02:00
Owner

Closes #1. See description in the commits.

Closes #1. See description in the commits.
Replaces the fixed 50-item pagination on the Library page with
IntersectionObserver-driven infinite scroll so every matching item is
reachable without manual paging.

* New JSON endpoint `GET /items/page` returns
  `{items, total, has_more, offset, limit}` for the same filter / sort
  combination the SSR page already uses; `limit` is capped at
  `MAX_PAGE_LIMIT = 100`.
* SSR library page keeps server-rendering the first slice (so no-JS still
  works), drops the `page` query param, removes the numeric pagination
  `<nav>`, and exposes the active filter set to JS via a `data-filters`
  attribute on the sentinel.
* New `initInfiniteScroll` module in `app/static/app.js` handles
  IntersectionObserver fetch, dedupe by `data-item-id`, end-of-results
  marker, error + retry, and auto-refresh pause while a fetch is pending.
* Selection toolbar re-queries checkboxes on update so streamed-in cards
  participate in bulk selection.
* Tests cover slice, has_more boundary, limit cap, filter and sort
  round-trip, and a regression that the SSR page no longer exposes the
  numeric pagination block.

Refs #1
Merge main to pick up dev-deploy workflow
All checks were successful
Deploy Development Branch / deploy-dev (push) Successful in 48s
d259c33bba
fix(library): emit HTML-safe data-filters and clean up skeleton
All checks were successful
Deploy Development Branch / deploy-dev (push) Successful in 42s
fe70261378
The first round of the infinite-scroll feature dropped a raw JSON string
into the data-filters attribute via Jinja's |tojson filter. The
filter marks its output as MarkupSafe so the quotes inside the payload
were not escaped; the browser then closed the attribute at the first
inner quote ({) and initInfiniteScroll ended up with a payload
it could not parse, so no follow-up /items/page fetch ever fired.
The three skeleton blocks next to the sentinel were also laid out as a
3-column grid that read as three empty boxes.

* Serialise the filter payload in app/main.py and pass the escaped
  JSON string to the template (filters_payload); Jinja still escapes
  it on output so the browser sees {&#34;q&#34;: ...}.
* Render the loading skeleton as a single contained card-shaped block
  (flex column, max-width, surface card background) instead of three
  loose skeletons in a grid.

Refs #1
fix(library): keep infinite-scroll sentinel visually empty
All checks were successful
Deploy Development Branch / deploy-dev (push) Successful in 42s
dd9ec00c66
The sentinel was rendered as a small block between the card grid and
the end-of-results marker, so a brief empty rectangle was visible while
the user scrolled and waited for the next slice to load.

* Sentinel is now a zero-height marker that the IntersectionObserver
  watches but never occupies layout space.
* Skeleton loading state only renders while a fetch is in flight
  (status hidden by default, shown only during fetchNext and
  hidden again in the finally block).
* IntersectionObserver is disconnected once the list is exhausted so
  it cannot keep firing after the end marker is shown.

Refs #1
fix(library): force hidden skeleton and end marker with CSS
All checks were successful
Deploy Development Branch / deploy-dev (push) Successful in 55s
78c4732da7
The HTML hidden attribute applies display: none at zero
specificity, but our flex / text-align rules on the loading skeleton
and end-of-results marker override it. The result was a brief flash of
the three skeleton boxes (and the end marker) on first paint before
the JS module ran statusEl.hidden = true.

Add explicit [hidden] overrides so the markup hidden attribute
always wins until the JS module flips it back on for the loading state.

Refs #1
fix(library): drop the loading skeleton — it read as a layout error
All checks were successful
Deploy Development Branch / deploy-dev (push) Successful in 40s
Clean Up Development Branch / cleanup-dev (pull_request) Successful in 19s
4978a681ad
The three skeleton boxes were meant as a loading indicator but they
looked like broken layout, not a loading state. The next slice of cards
appends fast enough that the user does not need a placeholder; only
the end-of-results marker and the error/retry block need to be visible.

* Remove infinite-scroll-status and its skeleton children from the
  template.
* Drop the statusEl query + show/hide toggles in the JS module.
* Strip the matching CSS rules.

Refs #1
vmruiz merged commit 92f2f117cd into main 2026-08-23 18:47:40 +02:00
Sign in to join this conversation.
No reviewers
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!8
No description provided.