Feature: Articles list pagination As a verified RenewCMS user with many articles I want the Articles list split into pages with a page-size menu, server-side search and tabs, and shareable URLs So that I can browse a large list quickly and come back to the same page Background: Given /article is the verified Articles route (features/articles_list.feature), per docs/design/routes.md, docs/design/screens/articles.md group "Articles: pagination" and the Paginator in docs/design/components.md And the route stays /article (the design's /articles is not adopted) And the page requests GET /v1/article/?format=html&page=P&size=S&q=Q&status=T and reads {items, total, page, size, counts} (features/articles_pagination_api.feature); the tab counts "All", "Online", "Drafts" come from counts and do not change while searching And page, size, q, status (tab) and sort live in the URL: ?page=&size=&q=&status=&sort=; the defaults (page 1, size 25, no q, tab All, sort updated_at:desc) are omitted from the URL and from the request only where the API default applies And the sortable columns are Title (title), Status (status), Created (created_at) and Updated (updated_at); a header activation sends sort=:, the active column shows an arrow icon for its direction, and sorting is done by the server over the whole list, never inside the loaded page And an invalid sort in the URL falls back to updated_at:desc and is replaced in the URL And the table caption reads "Articles, sorted by last updated, newest first" for the default sort and "Articles, sorted by {column}, {ascending|descending}" otherwise, with a French entry in messages.ts And the mobile card list has no sort control; the sort parameter is still honoured from the URL there And an invalid or missing size falls back to 25 and an invalid page (not an integer >= 1) falls back to 1, both replaced in the URL And a change to search, tab, sort or 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) 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" 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 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 this slice amends features/articles_list.feature: search, tabs and sort are no longer computed client-side over the full list; the list, the counts and the empty states come from the server response And HomeView, ArticleView and useArticles.listArticles migrate to the envelope: they read items (and counts or total where they count) and send explicit paging; no caller reads a bare array any more And the fixture below is total 312, size 25 (13 pages) unless stated; the frames draw at most 10 rows Scenario: 1 First page Given GET /v1/article/?format=html&page=1&size=25 resolves 200 with 25 items and total 312 When the user navigates to /article 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: 2 Middle page Given GET ...&page=7&size=25 resolves 200 with 25 items and total 312 When the user navigates to /article?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: 3 Last page Given GET ...&page=13&size=25 resolves 200 with 12 items and total 312 When the user navigates to /article?page=13 Then the summary reads "Showing 301–312 of 312" And Next and Last are disabled Scenario: 4 Single page Given GET /v1/article/ resolves 200 with 6 items, total 6 and counts {all 6, online 4, draft 2} When the user navigates to /article Then the summary reads "Showing 1–6 of 6" and the page-size select stays And the paginator (First ... Last) is not rendered And the tabs read "All 6" "Online 4" "Drafts 2" Scenario: 5 Loading next page Given the first page has loaded and GET ...&page=2 has not resolved When the user activates Next Then skeleton rows keep the table 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 rows and summary of page 2 appear and the URL becomes /article?page=2 Scenario Outline: 6 Error loading a page keeps the previous rows Given page 6 is shown When the user activates Next and GET ...&page=7 Then the page-6 rows stay, the URL stays /article?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 rows, the URL becomes ?page=7 and the notice goes Examples: | failure | | resolves with a 500 response | | never reaches the server | Scenario: 7 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 /article?page=99 Then the URL is replaced (no history entry) by /article?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 (3) is shown Scenario: 7b Out of range with total 0 shows the empty state, no redirect Given GET ...&page=5 resolves 200 with items [] and total 0 When the user navigates to /article?page=5 Then the "No articles yet" empty state shows and the URL is /article Scenario: 8 Search resets to page 1 Given the user is on /article?page=7 When the user types "plaster" in the search field and the debounce elapses Then GET ...&q=plaster&page=1 is sent And on success the URL is /article?q=plaster (page omitted) And the tab counts are unchanged And the same reset happens for a tab change, a sort change and a page-size change Scenario: 9 Empty after search Given GET ...&q=tiles resolves 200 with items [], total 0 and counts {all 6, online 4, draft 2} When the user searches "tiles" Then the paginator and the summary are hidden And the no-results state shows "No articles match "tiles"", "Check the spelling, or search for a shorter part of the title." and "Clear search" And the tabs still read "All 6" "Online 4" "Drafts 2" Scenario Outline: 10 Page size menu Given the user is on /article?page=3 When the user opens the page-size select (it opens upward) and chooses "