Feature: Media page pagination As a verified owner or admin with many images I want the Media library split into pages with a page-size menu and shareable URLs So that I can browse a large library quickly and come back to the same page Background: Given /media is the verified Media route (features/media_browse.feature), per docs/design/routes.md "Pagination in the URL (Articles, Media)" and docs/design/screens/media.md states 44-52 And the Paginator and PageSizeSelect are the existing components used by Articles (AppPaginator.vue); no new component is created And the page requests GET /v1/media/?q=Q&filter=F&sort=S&variants=V&page=P&size=Z and reads {items, total, page, size, counts} (features/media_pagination_api.feature); q and variants are sent only when set; page and size are always sent explicitly; the tab counts come from counts and do not change while searching And page and size live in the URL: ?page=&size=, next to the existing q, filter, sort, variants and view; the defaults (page 1, size 25) are omitted from the URL And an invalid or missing size falls back to the stored size (features/media_page_size_memory.feature) or 25, and an invalid page (not an integer >= 1) falls back to 1, both silently replaced in the URL with history.replace and never shown as an error And a change to search, tab, sort, view, the variants filter or the page size resets page to 1 (and drops it from the URL); search keeps its 120 ms debounce And the URL changes when a response succeeds, not when the request is sent And the summary reads "Showing {from}–{to} of {total}" with locale-formatted numbers, from total (the filtered count), under the grid or list And the Paginator has First, Previous, page numbers with ellipses marking skipped ranges, Next, Last, and the page-size select "5 per page", "25 per page", "50 per page", "100 per page" (it opens upward) And the current page uses --rc-sand fill, --rc-on-sand text and a --ui-primary border and has aria-current="page"; disabled controls are at 40 % opacity; buttons are 32 px (44 px on mobile) And on mobile the paginator is Previous, "Page N of M" and Next And the paginator is hidden when total fits on one page; the summary and the page-size select stay And the paginator sits under the grid in grid view and under the table (md and up) or card list (below md) in list view; its exact placement is taken from the board frame docs/design/source/MediaFrame.dc.html, not from components.md prose And keyboard: Tab reaches the page-size select, first, previous, each page, next, last; Enter or Space activates; ArrowDown opens the page-size list, Enter selects, Esc closes; every control shows a 2 px --ui-primary focus outline with a 2 px offset And the Duplicates tab (?filter=duplicates) is not paginated: it loads the whole duplicate set with size=100, requesting page 1, 2, ... until the loaded count reaches total, groups it by sha256 as today, shows no paginator, summary or page-size select, and drops page and size from the URL And "Select all" covers the images on the current page only and reads "{n} images" for that page; changing page, size, search, tab, sort or view clears the selection And the silent background refresh after a delete, upload or merge reloads the current page with no skeleton; when the current page has become empty and is past the last page, the list steps back to the last page and replaces the URL And the first load and a change of search, tab or sort keep the existing skeleton after 150 ms and the "Couldn't load your images" alert (media.md states 4 and 42); the page-change error below applies only when images are already shown And the fixture below is total 312, size 25 (13 pages) unless stated, with the board's own sample data Scenario: 44 First page Given GET /v1/media/?filter=all&sort=newest&page=1&size=25 resolves 200 with 25 items, total 312 and counts {all 312, unused 2, duplicates 2} When the user navigates to /media Then the summary reads "Showing 1–25 of 312" And First and Previous are disabled, page 1 is current, Next and Last are enabled And the URL has no page or size Scenario: 45 Middle page Given GET ...&page=7&size=25 resolves 200 with 25 items and total 312 When the user navigates to /media?page=7 Then the summary reads "Showing 151–175 of 312" And page 7 is current with --rc-sand / --rc-on-sand and aria-current="page" And ellipses mark the skipped ranges and every control is enabled Scenario: 46 Last page Given GET ...&page=13&size=25 resolves 200 with 12 items and total 312 When the user navigates to /media?page=13 Then the summary reads "Showing 301–312 of 312" And Next and Last are disabled Scenario: Single page Given GET /v1/media/ resolves 200 with 12 items, total 12 and counts {all 12, unused 2, duplicates 2} When the user navigates to /media Then the summary reads "Showing 1–12 of 12" and the page-size select stays And the paginator (First ... Last) is not rendered Scenario: 47 List view, desktop and mobile Given page 2 of 13 is shown with ?view=list Then at md width or more the paginator sits under the table with the page numbers And below md it sits under the card list as Previous, "Page 2 of 13", Next with 44 px controls and no page numbers And in grid view on mobile the paginator is the same Previous, "Page 2 of 13", Next Scenario: 48 Loading next page Given the first page has loaded and GET ...&page=2 has not resolved When the user activates Next Then skeleton cards keep the grid height and the list is aria-busy="true" And every paginator control is disabled and the summary reads "Loading page 2…" When the request resolves 200 Then the images and summary of page 2 appear and the URL becomes /media?page=2 Scenario Outline: 49 Error loading a page keeps the previous images Given page 6 is shown When the user activates Next and GET ...&page=7 Then the page-6 images stay, the URL stays /media?page=6 and the controls are re-enabled And a notice reads "Couldn't load page 7. Showing page 6." with a "Retry" action When the user activates "Retry" Then GET ...&page=7 is sent again and, on 200, page 7 replaces the images, the URL becomes ?page=7 and the notice goes Examples: | failure | | resolves with a 500 response | | never reaches the server | Scenario: 50 Page out of range redirects to the last page with a notice Given GET ...&page=99 resolves 200 with items [] and total 312 When the user navigates to /media?page=99 Then the URL is replaced (no history entry) by /media?page=13 and GET ...&page=13 is sent And the notice reads "That page doesn't exist. Showing the last page." And the last-page state (46) is shown Scenario: Out of range with total 0 shows the empty state, no redirect Given GET ...&page=5 resolves 200 with items [] and total 0 and counts.all 0 When the user navigates to /media?page=5 Then the "No images yet" empty state shows and the URL is /media Scenario: Out of range after a search with total 0 shows no results Given GET ...&q=zzzz&page=5 resolves 200 with items [], total 0 and counts.all 312 When the user navigates to /media?q=zzzz&page=5 Then the no-results state (media.md state 6) shows and the URL is /media?q=zzzz Scenario Outline: Search, tab, sort, view and variants changes reset to page 1 Given the user is on /media?page=7 When the user and the request resolves Then GET ... carries page=1 And on success the URL has no page And the tab counts are unchanged by a search Examples: | action | | types "wall" in the search field and the debounce elapses | | selects the "Unused" tab | | picks "Largest" in the sort menu | | activates the list icon in the grid toggle | | applies the "With variants" filter | Scenario Outline: 51 Page size menu Given the user is on /media?page=3 When the user opens the page-size select (it opens upward) and chooses "