fix(ui): tag autocomplete no longer shows stale wrong-prefix results (race)
The keystroke debounce cleared the timer but not an already-fired fetch, so a
slower earlier-prefix response ("s") could land after "sex" and overwrite the
dropdown with wrong-prefix matches (operator-flagged with a "sex"→Stockings/
Super Mario screenshot). Gate each autocomplete response on a useInflightToken
(cancel on every keystroke, isCurrent() after the await) so only the latest
query's results are applied.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CDgx8bQS5YrGRK76v8HUnM
This commit is contained in:
@@ -97,6 +97,7 @@
|
||||
import { computed, nextTick, onMounted, ref, watch } from 'vue'
|
||||
import { useTagStore } from '../../stores/tags.js'
|
||||
import { useSuggestionsStore } from '../../stores/suggestions.js'
|
||||
import { useInflightToken } from '../../composables/useInflightToken.js'
|
||||
import FandomPicker from './FandomPicker.vue'
|
||||
|
||||
const emit = defineEmits(['pick-existing', 'pick-new', 'accept-suggestion', 'cancel'])
|
||||
@@ -183,17 +184,26 @@ const parsed = computed(() => {
|
||||
const parsedKind = computed(() => parsed.value.kind)
|
||||
const parsedName = computed(() => parsed.value.name)
|
||||
|
||||
// Inflight guard: the debounce only clears the TIMER, so once a fetch has fired
|
||||
// it still races later ones — a slower earlier-prefix response ("s") could land
|
||||
// after "sex" and overwrite the dropdown with stale, wrong-prefix matches
|
||||
// (operator-flagged 2026-07-06). Gate each response on a token so only the latest
|
||||
// query's results are applied.
|
||||
const acInflight = useInflightToken()
|
||||
let debounceId = null
|
||||
watch(query, () => {
|
||||
highlight.value = 0
|
||||
if (debounceId) clearTimeout(debounceId)
|
||||
acInflight.cancel()
|
||||
debounceId = setTimeout(async () => {
|
||||
const q = parsedName.value
|
||||
if (!q) { hits.value = []; return }
|
||||
// Autocomplete across ALL kinds. When the user typed a prefix the
|
||||
// matches list is naturally narrower because the parsed name is
|
||||
// shorter; we don't filter server-side by kind.
|
||||
hits.value = await store.autocomplete(q, null, 10)
|
||||
const t = acInflight.claim()
|
||||
const res = await store.autocomplete(q, null, 10)
|
||||
if (t.isCurrent()) hits.value = res
|
||||
}, 200)
|
||||
})
|
||||
|
||||
|
||||
Reference in New Issue
Block a user