- {{ $t('search.no_results', { query }) }}
+ {{ $t('search.no_results', { query: committedQuery }) }}
diff --git a/i18n/locales/en.json b/i18n/locales/en.json
index 5bc57ea17e..3f158967c4 100644
--- a/i18n/locales/en.json
+++ b/i18n/locales/en.json
@@ -71,7 +71,9 @@
"instant_search_off": "off",
"instant_search_turn_on": "turn on",
"instant_search_turn_off": "turn off",
- "instant_search_advisory": "{label} {state} — {action}"
+ "instant_search_advisory": "{label} {state} — {action}",
+ "more_results_available_npm": "There are {total} results for this search, but npm registry limits search output. Refine your query to narrow results.",
+ "more_results_available_algolia": "There are {total} results for this search. Algolia search is capped at 1,000 — switch to npm Registry for up to 5,000 results, or refine your query."
},
"nav": {
"main_navigation": "Main",
diff --git a/i18n/schema.json b/i18n/schema.json
index 4ed08a60b2..bdf2603b1f 100644
--- a/i18n/schema.json
+++ b/i18n/schema.json
@@ -219,6 +219,12 @@
},
"instant_search_advisory": {
"type": "string"
+ },
+ "more_results_available_npm": {
+ "type": "string"
+ },
+ "more_results_available_algolia": {
+ "type": "string"
}
},
"additionalProperties": false
diff --git a/shared/types/npm-registry.ts b/shared/types/npm-registry.ts
index 250a9218e6..f3eb429d6c 100644
--- a/shared/types/npm-registry.ts
+++ b/shared/types/npm-registry.ts
@@ -103,6 +103,8 @@ export interface PackageVersionInfo {
deprecated?: string
}
+export type SearchProvider = 'npm' | 'algolia'
+
/**
* Person/contact type extracted from @npm/types Contact interface
* Used for maintainers, authors, publishers
@@ -122,6 +124,7 @@ export interface NpmPerson {
export interface NpmSearchResponse {
isStale: boolean
objects: NpmSearchResult[]
+ totalUnlimited?: number
total: number
time: string
}
diff --git a/shared/types/preferences.ts b/shared/types/preferences.ts
index 1714c404d3..96ef6073fe 100644
--- a/shared/types/preferences.ts
+++ b/shared/types/preferences.ts
@@ -3,6 +3,8 @@
* Used for configurable columns, filtering, sorting, and pagination
*/
+import type { SearchProvider } from './npm-registry'
+
// View modes
export type ViewMode = 'cards' | 'table'
@@ -163,7 +165,7 @@ export const SORT_KEYS: SortKeyConfig[] = [
* - npm returns 1 for all detail scores, and score.final === searchScore (= relevance)
* - Algolia returns synthetic values (quality: 0|1, maintenance: 0, score: 0)
*/
-export const PROVIDER_SORT_KEYS: Record<'algolia' | 'npm', Set
> = {
+export const PROVIDER_SORT_KEYS: Record> = {
algolia: new Set(['relevance', 'downloads-week', 'updated', 'name']),
npm: new Set(['relevance', 'downloads-week', 'updated', 'name']),
}
From 4c453dd53760a3f02c71b05efff84f7ec0d6405e Mon Sep 17 00:00:00 2001
From: Alex Korytskyi
Date: Tue, 10 Mar 2026 23:33:59 +0000
Subject: [PATCH 2/5] fix: remove log, add typesafe condition in search
---
app/pages/search.vue | 7 ++++---
1 file changed, 4 insertions(+), 3 deletions(-)
diff --git a/app/pages/search.vue b/app/pages/search.vue
index a81c4c8835..74a4be1c5a 100644
--- a/app/pages/search.vue
+++ b/app/pages/search.vue
@@ -253,9 +253,10 @@ const effectiveTotal = computed(() => {
})
const resultsLimitAppliedText = computed(() => {
- console.log(effectiveTotal.value, visibleResults.value?.totalUnlimited)
- if (isRelevanceSort.value && effectiveTotal.value < visibleResults.value?.totalUnlimited) {
- const total = { total: $n(visibleResults.value?.totalUnlimited) }
+ const totalUnlimited = visibleResults.value?.totalUnlimited ?? 0
+
+ if (isRelevanceSort.value && effectiveTotal.value < totalUnlimited) {
+ const total = { total: $n(totalUnlimited) }
return searchProvider.value === 'npm'
? $t('search.more_results_available_npm', total)
From 62b7f6e9969eef726f1cc45bd23d17e2b29730d2 Mon Sep 17 00:00:00 2001
From: Alex Korytskyi
Date: Thu, 12 Mar 2026 20:04:34 +0000
Subject: [PATCH 3/5] fix: max available results hint fixed og search page
---
app/pages/search.vue | 87 ++++++++++++++++++++++++++++----------------
1 file changed, 55 insertions(+), 32 deletions(-)
diff --git a/app/pages/search.vue b/app/pages/search.vue
index 74a4be1c5a..9a6694942e 100644
--- a/app/pages/search.vue
+++ b/app/pages/search.vue
@@ -253,14 +253,15 @@ const effectiveTotal = computed(() => {
})
const resultsLimitAppliedText = computed(() => {
- const totalUnlimited = visibleResults.value?.totalUnlimited ?? 0
+ const totalUnlimited = results.value?.totalUnlimited ?? 0
+ const total = results.value?.total ?? 0
- if (isRelevanceSort.value && effectiveTotal.value < totalUnlimited) {
- const total = { total: $n(totalUnlimited) }
+ if (isRelevanceSort.value && total < totalUnlimited) {
+ const totalTrans = { total: $n(totalUnlimited) }
return searchProvider.value === 'npm'
- ? $t('search.more_results_available_npm', total)
- : $t('search.more_results_available_algolia', total)
+ ? $t('search.more_results_available_npm', totalTrans)
+ : $t('search.more_results_available_algolia', totalTrans)
}
// do not show hint if results limit is not reached
return ''
@@ -407,14 +408,16 @@ const exactMatchType = computed<'package' | 'org' | 'user' | null>(() => {
const suggestionCount = computed(() => validatedSuggestions.value.length)
const totalSelectableCount = computed(() => suggestionCount.value + resultCount.value)
+function isElementVisible(el: HTMLElement) {
+ return el.getClientRects().length > 0
+}
+
/**
* Get all focusable result elements in DOM order (suggestions first, then packages)
*/
function getFocusableElements(): HTMLElement[] {
- const isVisible = (el: HTMLElement) => el.getClientRects().length > 0
-
const suggestions = Array.from(document.querySelectorAll('[data-suggestion-index]'))
- .filter(isVisible)
+ .filter(isElementVisible)
.sort((a, b) => {
const aIdx = Number.parseInt(a.dataset.suggestionIndex ?? '0', 10)
const bIdx = Number.parseInt(b.dataset.suggestionIndex ?? '0', 10)
@@ -422,7 +425,7 @@ function getFocusableElements(): HTMLElement[] {
})
const packages = Array.from(document.querySelectorAll('[data-result-index]'))
- .filter(isVisible)
+ .filter(isElementVisible)
.sort((a, b) => {
const aIdx = Number.parseInt(a.dataset.resultIndex ?? '0', 10)
const bIdx = Number.parseInt(b.dataset.resultIndex ?? '0', 10)
@@ -449,7 +452,7 @@ async function navigateToPackage(packageName: string) {
const pendingEnterQuery = shallowRef(null)
// Watch for results to navigate when Enter was pressed before results arrived
-watch(displayResults, results => {
+watch(displayResults, ([firstResult]) => {
if (!pendingEnterQuery.value) return
// Check if input is still focused (user hasn't started navigating or clicked elsewhere)
@@ -459,7 +462,6 @@ watch(displayResults, results => {
}
// Navigate if first result matches the query that was entered
- const firstResult = results[0]
// eslint-disable-next-line no-console
console.log('[search] watcher fired', {
pending: pendingEnterQuery.value,
@@ -764,16 +766,29 @@ onBeforeUnmount(() => {
/>
- {{
+ {{
$t(
'search.found_packages',
{ count: $n(visibleResults.total) },
visibleResults.total,
)
- }}
+ }}
+
+
+
+
+
{{
@@ -786,25 +801,33 @@ onBeforeUnmount(() => {
- {{
- $t(
- 'filters.count.showing_paginated',
- {
- pageSize: Math.min(preferredPageSize, effectiveTotal),
- count: $n(effectiveTotal),
- },
- effectiveTotal,
- )
- }}
-
-
-
+ {{
+ $t(
+ 'filters.count.showing_paginated',
+ {
+ pageSize: Math.min(preferredPageSize, effectiveTotal),
+ count: $n(effectiveTotal),
+ },
+ effectiveTotal,
+ )
+ }}
+
+
+
+
+
+
From 640792989f217c431752fdf26a0f7f9bd185c190 Mon Sep 17 00:00:00 2001
From: Alex Korytskyi