From 1ce8510850ffeb35587de85e388fed0d91b52534 Mon Sep 17 00:00:00 2001 From: Atriiy Date: Tue, 17 Mar 2026 10:59:37 +0800 Subject: [PATCH 1/8] feat: wrap committedSearchQuery update in a debounce --- app/composables/useGlobalSearch.ts | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/app/composables/useGlobalSearch.ts b/app/composables/useGlobalSearch.ts index 77367a049a..b6f38e2422 100644 --- a/app/composables/useGlobalSearch.ts +++ b/app/composables/useGlobalSearch.ts @@ -4,6 +4,8 @@ import { debounce } from 'perfect-debounce' // Pages that have their own local filter using ?q const pagesWithLocalFilter = new Set(['~username', 'org']) +const SEARCH_DEBOUNCE_MS = 250 + export function useGlobalSearch(place: 'header' | 'content' = 'content') { const { settings } = useSettings() const { searchProvider } = useSearchProvider() @@ -27,10 +29,14 @@ export function useGlobalSearch(place: 'header' | 'content' = 'content') { // Syncs instantly when instantSearch is on, but only on Enter press when off const committedSearchQuery = useState('committed-search-query', () => searchQuery.value) + const commitSearchQuery = debounce((val: string) => { + committedSearchQuery.value = val + }, SEARCH_DEBOUNCE_MS) + // This is basically doing instant search as user types watch(searchQuery, val => { if (settings.value.instantSearch) { - committedSearchQuery.value = val + commitSearchQuery(val) } }) @@ -71,10 +77,11 @@ export function useGlobalSearch(place: 'header' | 'content' = 'content') { }) } - const updateUrlQuery = debounce(updateUrlQueryImpl, 250) + const updateUrlQuery = debounce(updateUrlQueryImpl, SEARCH_DEBOUNCE_MS) function flushUpdateUrlQuery() { // Commit the current query when explicitly submitted (Enter pressed) + commitSearchQuery.cancel() committedSearchQuery.value = searchQuery.value // When instant search is off the debounce queue is empty, so call directly if (!settings.value.instantSearch) { From 25b8ea2ff685b166380034077c60f8cf105f6dc6 Mon Sep 17 00:00:00 2001 From: Atriiy Date: Tue, 17 Mar 2026 12:51:02 +0800 Subject: [PATCH 2/8] fix: fix undebounced query --- app/pages/search.vue | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/pages/search.vue b/app/pages/search.vue index eb424d7ddc..99530ff8e3 100644 --- a/app/pages/search.vue +++ b/app/pages/search.vue @@ -357,7 +357,7 @@ const showClaimPrompt = computed(() => { return ( isValidPackageName.value && packageAvailability.value?.available === true && - packageAvailability.value.name === query.value.trim() && + packageAvailability.value.name === committedQuery.value.trim() && (!isConnected.value || (isConnected.value && canPublishToScope.value)) && status.value !== 'pending' ) From b9992e8fcc223b2335d4f9aca855cf8124957803 Mon Sep 17 00:00:00 2001 From: Atriiy Date: Tue, 17 Mar 2026 13:17:53 +0800 Subject: [PATCH 3/8] fix: tweaks showClaimPrompt to prevent it disappear during fetching --- app/pages/search.vue | 88 ++++++++++++++++++++++++++------------------ 1 file changed, 53 insertions(+), 35 deletions(-) diff --git a/app/pages/search.vue b/app/pages/search.vue index 99530ff8e3..5d7ed2a1ed 100644 --- a/app/pages/search.vue +++ b/app/pages/search.vue @@ -354,13 +354,19 @@ const canPublishToScope = computed(() => { // Show claim prompt when valid name, available, either not connected or connected and has permission const showClaimPrompt = computed(() => { - return ( - isValidPackageName.value && - packageAvailability.value?.available === true && - packageAvailability.value.name === committedQuery.value.trim() && - (!isConnected.value || (isConnected.value && canPublishToScope.value)) && - status.value !== 'pending' - ) + if (!isValidPackageName.value) return false + if (isConnected.value && !canPublishToScope.value) return false + + const avail = packageAvailability.value + + // Confirmed: availability result matches current committed query + if (avail?.available === true && avail.name === committedQuery.value.trim()) return true + + // Pending: a new fetch is in flight — keep the claim visible if the last known + // result was "available" so it doesn't flicker until new data arrives + if (status.value === 'pending' && avail?.available === true) return true + + return false }) const claimPackageModalRef = useTemplateRef('claimPackageModalRef') @@ -711,22 +717,28 @@ onBeforeUnmount(() => { status === 'success' " > -
- -
+
+ +
+
{ {{ $t('search.no_results', { query }) }}

-
- -
+ +
+ +
+