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
+51 -7
View File
@@ -10,6 +10,9 @@ import SearchWorker from '@/workers/searchWorker?worker'
let searchWorker: Worker | null = null let searchWorker: Worker | null = null
let searchId = 0 let searchId = 0
let searchStore: ReturnType<typeof useMainStore> | null = null 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 { function getSearchWorker(): Worker {
if (!searchWorker) { if (!searchWorker) {
@@ -18,14 +21,30 @@ function getSearchWorker(): Worker {
searchWorker.onmessage = (e) => { searchWorker.onmessage = (e) => {
if (!searchStore || e.data.id !== searchId) return // Stale result if (!searchStore || e.data.id !== searchId) return // Stale result
// Convert plain data back to Doc instances (constructor is now lightweight) // Convert plain data back to Doc instances
const docs = [] const docs = e.data.docs.map((d: any) => new Doc(d))
for (const d of e.data.docs) {
docs.push(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 searchStore.searchResults = docs
if (e.data.done) { if (e.data.done) {
// Clear the loading timer and hide spinner
if (loadingTimer) {
clearTimeout(loadingTimer)
loadingTimer = null
}
searchStore.searchLoading = false searchStore.searchLoading = false
} }
} }
@@ -138,15 +157,40 @@ export const useMainStore = defineStore('main', {
// Update query immediately so watchers know we're handling this // Update query immediately so watchers know we're handling this
this.query = query this.query = query
// Clear old results immediately - don't show stale data // Cancel pending timers
this.searchResults = [] if (loadingTimer) {
clearTimeout(loadingTimer)
loadingTimer = null
}
if (clearOldResultsTimer) {
clearTimeout(clearOldResultsTimer)
clearOldResultsTimer = null
}
if (!query) { if (!query) {
// Clear results only when search is closed
this.searchResults = []
this.searchLoading = false this.searchLoading = false
return return
} }
this.searchLoading = true // 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 }) worker.postMessage({ type: 'search', query, loc, id })
}, },
login(username: string, privileged: boolean) { login(username: string, privileged: boolean) {