Skip to content

fix: search results limit#1996

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

fix: search results limit#1996
alex-key wants to merge 10 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 13, 2026 8:53am
2 Skipped Deployments
Project Deployment Actions Updated (UTC)
docs.npmx.dev Ignored Ignored Preview Mar 13, 2026 8:53am
npmx-lunaria Ignored Ignored Mar 13, 2026 8:53am

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 60.46512% with 17 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 65.62% 10 Missing and 1 partial ⚠️
app/composables/npm/useAlgoliaSearch.ts 0.00% 6 Missing ⚠️

📢 Thoughts on this report? Let us know!

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Mar 10, 2026

📝 Walkthrough

Walkthrough

Adds a new exported SearchProvider type and a SearchResponse (now including totalUnlimited) to shared types; propagates totalUnlimited through search composables and refactors the search cache and response normalization in useSearch (introducing SEARCH_ENGINE_HITS_LIMIT and DEFAULT_INITIAL_SEARCH_LIMIT). Replaces hard‑coded pagination threshold with ALL_PAGES_VISIBLE_TRESHOLD and disables distant page buttons when threshold exceeded; localises displayed item numbers. Updates Algolia type aliasing in useAlgoliaSearch, removes an ARIA role from the package card keywords list, and adds i18n keys/messages for “more results available” hints.

Possibly related PRs

Suggested reviewers

  • danielroe
  • ghostdevv
  • maxchang3
🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description check ✅ Passed The PR description clearly describes the changeset, detailing the issues resolved (pagination limits and display), the implemented fixes, and the technical context.
Linked Issues check ✅ Passed The PR effectively addresses the linked issues by implementing strict result limits (1000 for Algolia, 5000 for npm), disabling pagination for pages beyond available results, adding tooltips for unavailable results, and fixing pagination display.
Out of Scope Changes check ✅ Passed All changes are directly related to fixing search result pagination and limits. Changes to type definitions, constants, UI components, and i18n translations are all in service of the stated objectives.

✏️ 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
📝 Coding Plan
  • Generate coding plan for human review comments

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.

Tip

CodeRabbit can use Trivy to scan for security misconfigurations and secrets in Infrastructure as Code files.

Add a .trivyignore file to your project to customize which findings Trivy reports.

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

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 (1)
i18n/locales/en.json (1)

75-76: Don't bake the cap values into the English copy.

Line 76 hard-codes 1,000 and 5,000, but the real caps already live in the search logic. If those limits change again, the UI text will drift even though the behaviour is correct. Passing the caps as i18n params would keep the message aligned with enforcement.


ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 924a2186-bd03-4d75-93cb-6460fa8e22e2

📥 Commits

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

📒 Files selected for processing (3)
  • app/pages/search.vue
  • i18n/locales/en.json
  • i18n/schema.json
🚧 Files skipped from review as they are similar to previous changes (1)
  • i18n/schema.json

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: 1


ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: dfc2700e-c643-45ae-8514-6e6c703ed936

📥 Commits

Reviewing files that changed from the base of the PR and between 62b7f6e and f3e2fe6.

📒 Files selected for processing (6)
  • app/composables/npm/search-utils.ts
  • app/composables/npm/useAlgoliaSearch.ts
  • app/composables/npm/useSearch.ts
  • i18n/locales/en.json
  • i18n/schema.json
  • shared/types/npm-registry.ts
🚧 Files skipped from review as they are similar to previous changes (4)
  • i18n/locales/en.json
  • i18n/schema.json
  • shared/types/npm-registry.ts
  • app/composables/npm/useAlgoliaSearch.ts

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.

🧹 Nitpick comments (1)
app/pages/search.vue (1)

455-474: Verify destructuring in watch callback behaves as intended.

The pattern watch(displayResults, ([firstResult]) => { ... }) destructures the first element from the watched array. This works because Vue's watch callback receives the new value (the array) as its first argument, and array destructuring extracts the first element.

However, this pattern may be unfamiliar to some developers. Consider adding a brief comment or using the more explicit form for clarity:

Optional: More explicit alternative
-watch(displayResults, ([firstResult]) => {
+watch(displayResults, (results) => {
+  const firstResult = results[0]
   if (!pendingEnterQuery.value) return

The current implementation is functionally correct and type-safe (thanks to optional chaining at line 470), so this is purely a readability suggestion.


ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 1c97bcca-a9e1-4f79-9cfc-ddd40356b415

📥 Commits

Reviewing files that changed from the base of the PR and between f3e2fe6 and 65b6d30.

📒 Files selected for processing (2)
  • app/composables/npm/useSearch.ts
  • app/pages/search.vue

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