Skip to content

fix: cap pagination to maximum fetchable results#2205

Open
thealxlabs wants to merge 3 commits intonpmx-dev:mainfrom
thealxlabs:fix/pagination-cap
Open

fix: cap pagination to maximum fetchable results#2205
thealxlabs wants to merge 3 commits intonpmx-dev:mainfrom
thealxlabs:fix/pagination-cap

Conversation

@thealxlabs
Copy link

🔗 Linked issue

resolves #1923

🧭 Context

The search page passed effectiveTotal (the raw result count from npm/Algolia, which can be 92,000+) directly to the pagination component. This caused the paginator to show thousands of pages, but navigation beyond page 20 (at 25 items per page with a 500-result cap) would silently fail because the API cannot return results past the EAGER_LOAD_SIZE limit.

📚 Description

Added a paginationTotal computed property that caps effectiveTotal with Math.min(effectiveTotal, EAGER_LOAD_SIZE[provider]). The pagination component now receives this capped value, so displayed page counts accurately reflect only the pages that can actually be fetched (max 20 pages at 25 items/page with a 500-item cap).

A unit test (test/unit/app/utils/pagination.spec.ts) verifies the capping logic for values below, at, and above the cap, and checks that the derived page count stays within the fetchable range.

@vercel
Copy link

vercel bot commented Mar 22, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
npmx.dev Ready Ready Preview, Comment Mar 22, 2026 6:47pm
2 Skipped Deployments
Project Deployment Actions Updated (UTC)
docs.npmx.dev Ignored Ignored Preview Mar 22, 2026 6:47pm
npmx-lunaria Ignored Ignored Mar 22, 2026 6:47pm

Request Review

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Mar 22, 2026

📝 Walkthrough

Walkthrough

This pull request addresses pagination display issues by introducing a paginationTotal computed value in app/pages/search.vue that caps the effectiveTotal based on EAGER_LOAD_SIZE[searchProvider.value]. The PaginationControls component is updated to use this capped value instead of the raw effectiveTotal, ensuring the pagination page count reflects only the maximum result set size that can be fetched for each provider. Comprehensive unit test coverage is added to verify the capping logic across different total values and providers.

Possibly related PRs

  • fix(ui): prevent scroll jump during pagination #1645: Modifies pagination and total-related logic in app/pages/search.vue—specifically reworked total/effectiveTotal handling to introduce displayResults/visibleResults previously; this PR builds upon the same code path by adding capping logic.

Suggested reviewers

  • danielroe
🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description check ✅ Passed The pull request description clearly relates to the changeset, explaining the problem of incorrect pagination counts and the solution of capping total results.
Linked Issues check ✅ Passed The code changes implement the capping logic to prevent pagination from displaying pages beyond the API's fetch limit, directly addressing the issue #1923 requirement.
Out of Scope Changes check ✅ Passed All changes are scoped to pagination capping functionality: modified search.vue pagination logic and added comprehensive unit tests for the capping behaviour.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2


ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: e52a54dc-e895-4fa3-935d-6af31767c168

📥 Commits

Reviewing files that changed from the base of the PR and between 7f2fc1a and c16a053.

📒 Files selected for processing (2)
  • app/pages/search.vue
  • test/unit/app/utils/pagination.spec.ts

Comment on lines +275 to +278
const paginationTotal = computed(() => {
const cap = EAGER_LOAD_SIZE[searchProvider.value]
return Math.min(effectiveTotal.value, cap)
})
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "Finding PaginationControls implementation..."
files="$(fd 'PaginationControls.*\.vue$' app || true)"
printf '%s\n' "$files"

if [ -n "$files" ]; then
  echo
  echo "Checking for internal clamping logic in PaginationControls..."
  rg -n -C3 'currentPage|maxPage|Math\.ceil|totalItems|total-items|watch\(' $files
fi

echo
echo "Checking page initialisation/clamping in search page..."
rg -n -C3 'initialPage|currentPage|paginationTotal|maxFetchablePages|Math\.ceil|watch\(' app/pages/search.vue

Repository: npmx-dev/npmx.dev

Length of output: 7069


Clamp currentPage against capped max pages to cover deep-link edge cases.

URL-derived currentPage can exceed the capped pagination range (e.g., ?page=999 from stale links). Whilst PaginationControls clamps page changes via its internal goToPage() guard, the initial currentPage value set on mount from the URL bypasses this check, resulting in an invalid state where the displayed pagination text and item range become incoherent (e.g., "Showing items 24976 to 500" when total is capped at 500).

Add a watch to clamp currentPage when paginationTotal or preferredPageSize changes:

Suggested clamp in app/pages/search.vue
 const paginationTotal = computed(() => {
   const cap = EAGER_LOAD_SIZE[searchProvider.value]
   return Math.min(effectiveTotal.value, cap)
 })
+
+const maxFetchablePages = computed(() =>
+  Math.max(1, Math.ceil(paginationTotal.value / preferredPageSize.value)),
+)
+
+watch([maxFetchablePages, currentPage], ([maxPages, page]) => {
+  if (page > maxPages) {
+    currentPage.value = maxPages
+    updateUrlPage(maxPages)
+  }
+})
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const paginationTotal = computed(() => {
const cap = EAGER_LOAD_SIZE[searchProvider.value]
return Math.min(effectiveTotal.value, cap)
})
const paginationTotal = computed(() => {
const cap = EAGER_LOAD_SIZE[searchProvider.value]
return Math.min(effectiveTotal.value, cap)
})
const maxFetchablePages = computed(() =>
Math.max(1, Math.ceil(paginationTotal.value / preferredPageSize.value)),
)
watch([maxFetchablePages, currentPage], ([maxPages, page]) => {
if (page > maxPages) {
currentPage.value = maxPages
updateUrlPage(maxPages)
}
})

Comment on lines +12 to +17
const EAGER_LOAD_SIZE = { algolia: 500, npm: 500 } as const

function paginationTotal(effectiveTotal: number, provider: keyof typeof EAGER_LOAD_SIZE): number {
const cap = EAGER_LOAD_SIZE[provider]
return Math.min(effectiveTotal, cap)
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

This test re-implements production logic instead of exercising a shared source of truth.

Lines 12-17 duplicate both EAGER_LOAD_SIZE and the capping function. That can let regressions in app/pages/search.vue pass unnoticed. Prefer extracting the cap logic into a shared util and importing it in both the page and this spec.

Refactor direction (shared util + direct import in tests)
-const EAGER_LOAD_SIZE = { algolia: 500, npm: 500 } as const
-
-function paginationTotal(effectiveTotal: number, provider: keyof typeof EAGER_LOAD_SIZE): number {
-  const cap = EAGER_LOAD_SIZE[provider]
-  return Math.min(effectiveTotal, cap)
-}
+import { EAGER_LOAD_SIZE, getPaginationTotal } from '~/utils/pagination'
-    expect(paginationTotal(100, 'npm')).toBe(100)
+    expect(getPaginationTotal(100, 'npm')).toBe(100)

(Apply same replacement across the file, and use the same util in app/pages/search.vue.)

@codecov
Copy link

codecov bot commented Mar 22, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ All tests successful. No failed tests found.

📢 Thoughts on this report? Let us know!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Search result pagination displays incorrect page count with empty pages

1 participant