Debouncing to avoid UI flicker while typing in search.

This commit is contained in:
2026-01-31 22:42:16 +00:00
parent d15bfc86c4
commit feff202161
+50 -6
View File
@@ -10,6 +10,9 @@ import SearchWorker from '@/workers/searchWorker?worker'
let searchWorker: Worker | null = null
let searchId = 0
let searchStore: ReturnType<typeof useMainStore> | null = null
let loadingTimer: ReturnType<typeof setTimeout> | null = null
let clearOldResultsTimer: ReturnType<typeof setTimeout> | null = null
let lastResultUpdate = 0
function getSearchWorker(): Worker {
if (!searchWorker) {
@@ -18,14 +21,30 @@ function getSearchWorker(): Worker {
searchWorker.onmessage = (e) => {
if (!searchStore || e.data.id !== searchId) return // Stale result
// Convert plain data back to Doc instances (constructor is now lightweight)
const docs = []
for (const d of e.data.docs) {
docs.push(new Doc(d))
// Convert plain data back to Doc instances
const docs = e.data.docs.map((d: any) => new Doc(d))
// Cancel the clear-old-results timer since we have new results
if (clearOldResultsTimer) {
clearTimeout(clearOldResultsTimer)
clearOldResultsTimer = null
}
// Throttle rapid intermediate updates to reduce UI flicker
const now = performance.now()
if (!e.data.done && now - lastResultUpdate < 50) {
return // Skip intermediate update if too recent
}
lastResultUpdate = now
searchStore.searchResults = docs
if (e.data.done) {
// Clear the loading timer and hide spinner
if (loadingTimer) {
clearTimeout(loadingTimer)
loadingTimer = null
}
searchStore.searchLoading = false
}
}
@@ -138,15 +157,40 @@ export const useMainStore = defineStore('main', {
// Update query immediately so watchers know we're handling this
this.query = query
// Clear old results immediately - don't show stale data
this.searchResults = []
// Cancel pending timers
if (loadingTimer) {
clearTimeout(loadingTimer)
loadingTimer = null
}
if (clearOldResultsTimer) {
clearTimeout(clearOldResultsTimer)
clearOldResultsTimer = null
}
if (!query) {
// Clear results only when search is closed
this.searchResults = []
this.searchLoading = false
return
}
// Keep old results briefly to avoid flicker on fast cached searches
// But clear them after 50ms if no new results have arrived
clearOldResultsTimer = setTimeout(() => {
if (searchId === id) {
this.searchResults = []
}
clearOldResultsTimer = null
}, 50)
// Delay showing loading indicator to avoid flicker on fast searches
loadingTimer = setTimeout(() => {
if (searchId === id) { // Still the current search
this.searchLoading = true
}
loadingTimer = null
}, 100)
worker.postMessage({ type: 'search', query, loc, id })
},
login(username: string, privileged: boolean) {