Skip to content

fix: search results limit#1996

Open
alex-key wants to merge 4 commits intonpmx-dev:mainfrom
alex-key:fix/search-results-limit
Open

fix: search results limit#1996
alex-key wants to merge 4 commits intonpmx-dev:mainfrom
alex-key:fix/search-results-limit

Conversation

@alex-key
Copy link
Contributor

@alex-key alex-key commented Mar 8, 2026

🔗 Linked issue

Resolves #1923, #1921

🧭 Context

Search results pagination worked with errors for outputs with many items. There are issues with results for 1001+ items.
After investigation I've found the following limits:

  • Algolia (search endpoint) is strictly limited to 1000 hits(see jsDelivr that uses Algolia has only 100 pages of 10 items while 180k results available). See in Algolia docs. This means that we should limit our output on Algolia engine to 1000 hits. Possible solutions: use browse endpoint (see docs), but it is not available in current Algolia index used by npmx
  • npmRegistry is strictly limited to 5000 hits. After 5001 hit npm starts to return results from index 0 (does not fail or throw an error, just duplicates response). I suppose there is no workaround for that.

📚 Description

The following fixes implemented:

  • Added strict limit of 1000 results for Algolia and 5000 for npmRegistry
  • Disabled the button for the last page in pagination. Click on it starts loading of multipe queries in a row, which usually leads to HTTP 429 "Too Many Requests" and "No results" in UI
  • Added a tooltip on search results page which shown in case there are more results available
  • Fixed numbers formatting in pagination widget
  • Fixed empty state copy on search results

@vercel
Copy link

vercel bot commented Mar 8, 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 10, 2026 11:37pm
2 Skipped Deployments
Project Deployment Actions Updated (UTC)
docs.npmx.dev Ignored Ignored Preview Mar 10, 2026 11:37pm
npmx-lunaria Ignored Ignored Mar 10, 2026 11:37pm

Request Review

@github-actions
Copy link

github-actions bot commented Mar 8, 2026

Lunaria Status Overview

🌕 This pull request will trigger status changes.

Learn more

By default, every PR changing files present in the Lunaria configuration's files property will be considered and trigger status changes accordingly.

You can change this by adding one of the keywords present in the ignoreKeywords property in your Lunaria configuration file in the PR's title (ignoring all files) or by including a tracker directive in the merged commit's description.

Tracked Files

File Note
i18n/locales/en.json Source changed, localizations will be marked as outdated.
Warnings reference
Icon Description
🔄️ The source for this localization has been updated since the creation of this pull request, make sure all changes in the source have been applied.

@codecov
Copy link

codecov bot commented Mar 8, 2026

Codecov Report

❌ Patch coverage is 53.84615% with 12 lines in your changes missing coverage. Please review.
✅ All tests successful. No failed tests found.

Files with missing lines Patch % Lines
app/composables/npm/useSearch.ts 55.55% 8 Missing ⚠️
app/composables/npm/useAlgoliaSearch.ts 0.00% 4 Missing ⚠️

📢 Thoughts on this report? Let us know!

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Mar 10, 2026

📝 Walkthrough

Walkthrough

This pull request implements search result pagination improvements by introducing a configurable results limit per search provider. Changes include adding a totalUnlimited property to track uncapped result counts whilst clamping the displayed total via SEARCH_ENGINE_HITS_LIMIT constants (Algolia: 1000, npm: 5000). The code centralises pagination threshold logic via ALL_PAGES_VISIBLE_TRESHOLD, disables page buttons beyond the threshold, localises pagination numbers, and exports a shared SearchProvider type across composables. UI enhancements include a tooltip indicating when results are limited and improved pagination controls.

Possibly related PRs

Suggested reviewers

  • danielroe
  • knowler
🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description check ✅ Passed The PR description clearly explains the context, investigation findings about backend limits, and the specific fixes implemented across multiple components.
Linked Issues check ✅ Passed The PR addresses the core requirements from issues #1923 and #1921: enforcing backend result limits (1000 for Algolia, 5000 for npm), disabling pagination to prevent invalid pages, adding a tooltip for additional results, and fixing number formatting.
Out of Scope Changes check ✅ Passed All changes relate directly to resolving the pagination and search limit issues. Type refactorings (SearchProvider consolidation) are supporting infrastructure to enable the limit enforcement.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Tip

Try Coding Plans. Let us write the prompt for your AI agent so you can ship faster (with fewer bugs).
Share your feedback on Discord.


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

🧹 Nitpick comments (2)
app/components/PaginationControls.vue (1)

5-5: Rename the threshold constant before the typo spreads further.

ALL_PAGES_VISIBLE_TRESHOLD is misspelt, which makes the intent look accidental and makes future searches/reuse harder. Please rename it to ALL_PAGES_VISIBLE_THRESHOLD while it is still local to this component.

As per coding guidelines, **/*.{ts,tsx,vue} should "Use clear, descriptive variable and function names".

Also applies to: 68-68, 104-104

app/composables/useSettings.ts (1)

5-5: Prefer the shared-types auto-import here.

app/composables/* already gets shared/types/* exports auto-imported, so this extra type import is unnecessary noise.

Based on learnings, exports from shared/types/* are auto-imported by Nuxt for composables and components, so explicit imports for those types are unnecessary.


ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: d48761f8-b319-4497-8e1e-c7c6b829a4d1

📥 Commits

Reviewing files that changed from the base of the PR and between 3712560 and 4c453dd.

📒 Files selected for processing (13)
  • app/components/Package/Card.vue
  • app/components/PaginationControls.vue
  • app/composables/npm/search-utils.ts
  • app/composables/npm/useAlgoliaSearch.ts
  • app/composables/npm/useSearch.ts
  • app/composables/npm/useUserPackages.ts
  • app/composables/useGlobalSearch.ts
  • app/composables/useSettings.ts
  • app/pages/search.vue
  • i18n/locales/en.json
  • i18n/schema.json
  • shared/types/npm-registry.ts
  • shared/types/preferences.ts
💤 Files with no reviewable changes (1)
  • app/components/Package/Card.vue

Comment on lines +102 to +105
// disable last page button to prevent TOO MANY REQUESTS error
function isPageButtonDisabled(page: number): boolean {
return totalPages.value > ALL_PAGES_VISIBLE_TRESHOLD && page > currentPage.value + 2
}
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

Expose the new disabled state visually on numbered page buttons.

The guard works, but the disabled page button still looks like a normal clickable button. That makes the new last-page restriction feel broken rather than intentional.

🎯 Proposed fix
-            class="min-w-[32px] h-8 px-2 font-mono text-sm rounded transition-colors duration-200 focus-visible:ring-2 focus-visible:ring-fg focus-visible:ring-offset-1"
+            class="min-w-[32px] h-8 px-2 font-mono text-sm rounded transition-colors duration-200 disabled:opacity-40 disabled:cursor-not-allowed focus-visible:ring-2 focus-visible:ring-fg focus-visible:ring-offset-1"

Also applies to: 207-208

Comment on lines +168 to +169
totalUnlimited: response.nbHits ?? 0,
total: Math.min(SEARCH_ENGINE_HITS_LIMIT.algolia, response.nbHits ?? 0),
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

Keep the Algolia cap contract consistent across helpers.

search() now returns a capped total plus totalUnlimited, but searchByOwner() in this file still returns the raw serverTotal as total. For owners with more than 1,000 packages, that will show a larger count than the list can ever contain, with no way to reach the missing results.

Suggested follow-up
-    const max = options.maxResults ?? 1000
+    const max = options.maxResults ?? SEARCH_ENGINE_HITS_LIMIT.algolia
…
     return {
       isStale: false,
       objects: allHits.map(hitToSearchResult),
-      total: serverTotal,
+      total: Math.min(max, serverTotal),
+      totalUnlimited: serverTotal,
       time: new Date().toISOString(),
     }

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