.vbw-planning/milestones/ui-fixes-and-smart-scraping/phases/03-dashboard-pagination/01-PLAN.md
Extend SourceMonitor::Pagination::Paginator with total_count and total_pages fields, then extract a reusable pagination partial that renders page numbers, jump-to-page form, and prev/next navigation.
What: Extend Pagination::Result struct with total_count and total_pages fields. Modify Paginator#paginate to compute these values. For ActiveRecord::Relation scopes, use .count (single DB query). For Array scopes, use .size.
Files to modify:
lib/source_monitor/pagination/paginator.rbImplementation details:
total_count and total_pages to the Result struct keyword_inittotal_pages method to Result: (total_count.to_f / per_page).ceilPaginator#paginate, compute total_count before fetching records:
scope.count (does SELECT COUNT(*))Array(scope).sizetotal_count into Result. total_pages is derived.total_count query happens once, separate from the offset/limit fetchtotal_pages convenience method on Result that computes [1, (total_count.to_f / per_page).ceil].maxAcceptance criteria:
result.total_count returns the total number of records in the scoperesult.total_pages returns ceil(total_count / per_page), minimum 1has_next_page?, has_previous_page?, next_page, previous_page still works unchangedWhat: Add tests to the existing paginator test file covering the new fields.
Files to modify:
test/lib/source_monitor/pagination/paginator_test.rbTest cases:
test "provides total_count and total_pages for relation scope" -- 6 items, per_page 3 -> total_count 6, total_pages 2test "provides total_count and total_pages for array scope" -- Array of 10 items, per_page 4 -> total_count 10, total_pages 3test "total_pages is at least 1 for empty scope" -- Empty scope -> total_count 0, total_pages 1test "total_count does not affect existing pagination behavior" -- Verify has_next_page/has_previous_page still correct alongside total_countAcceptance criteria:
What: Create a reusable pagination partial at app/views/source_monitor/shared/_pagination.html.erb that renders:
The partial accepts these locals:
paginator_result -- a Pagination::Result with total_count/total_pagesbase_path -- the URL path to link to (e.g., source_monitor.sources_path)extra_params -- hash of additional query params to preserve (search, filters, per_page)turbo_frame -- optional Turbo Frame target for the links (defaults to nil)Files to create:
app/views/source_monitor/shared/_pagination.html.erbImplementation details:
<form> with a number input and "Go" button, GET to base_path with page paramextra_params (search filters, per_page, sort)turbo_frame is provided, add data-turbo-frame to all links and formAcceptance criteria:
What: Add a helper method to ApplicationHelper that builds the page number windows (which page numbers to show, where to put ellipses) so the partial stays clean.
Files to modify:
app/helpers/source_monitor/application_helper.rbImplementation details:
pagination_page_numbers(current_page:, total_pages:, window: 2) method:gap symbols, e.g., [1, :gap, 4, 5, 6, 7, 8, :gap, 20]:gapAcceptance criteria:
pagination_page_numbers(current_page: 1, total_pages: 5) -> [1, 2, 3, 4, 5] (no gaps when small)pagination_page_numbers(current_page: 5, total_pages: 10) -> [1, :gap, 3, 4, 5, 6, 7, :gap, 10]pagination_page_numbers(current_page: 1, total_pages: 1) -> [1]pagination_page_numbers(current_page: 1, total_pages: 20) -> [1, 2, 3, :gap, 20]What: Unit tests for the pagination_page_numbers helper method.
Files to create:
test/helpers/source_monitor/pagination_helper_test.rbTest cases:
[1]Acceptance criteria:
This plan modifies:
lib/source_monitor/pagination/paginator.rb (paginator lib)test/lib/source_monitor/pagination/paginator_test.rb (paginator tests)app/views/source_monitor/shared/_pagination.html.erb (NEW partial)app/helpers/source_monitor/application_helper.rb (helper addition)test/helpers/source_monitor/pagination_helper_test.rb (NEW test)No overlap with Plan 02 (dashboard schedule files) or Plan 04 (stats query files).