import { defineStore } from 'pinia' import { ref, computed } from 'vue' import { useApi } from '../composables/useApi.js' import { useAsyncAction } from '../composables/useAsyncAction.js' import { useInflightToken } from '../composables/useInflightToken.js' const PAGE = 60 export const useArtistDirectoryStore = defineStore('artistDirectory', () => { const api = useApi() const cards = ref([]) const nextCursor = ref(null) const { loading, error, run } = useAsyncAction({ errorAs: 'message' }) const q = ref('') const platform = ref(null) let started = false // Has a load ATTEMPT completed yet? Gates the "No artists match" empty state // so it can't flash on the very first render (loading is still false before // onMounted fires the first loadMore) or between a query change and its fetch. const loaded = ref(false) // Typed "alice" then "alice bob" used to drop the second fetch // entirely (loading flag still true from the first), so the UI // showed alice results while the input said "alice bob". Inflight // token + reset() cancelling in-flight requests fixes both: the // first response is discarded, the second is fetched. Audit 2026-06-02. const inflight = useInflightToken() async function loadMore() { if (started && nextCursor.value === null) return const t = inflight.claim() await run(async () => { const params = { limit: PAGE } if (q.value) params.q = q.value if (platform.value) params.platform = platform.value if (nextCursor.value) params.cursor = nextCursor.value const body = await api.get('/api/artists/directory', { params }) if (!t.isCurrent()) return cards.value.push(...body.cards) nextCursor.value = body.next_cursor started = true }) if (t.isCurrent()) loaded.value = true } async function reset() { inflight.cancel() cards.value = [] nextCursor.value = null started = false loaded.value = false await loadMore() } function setQuery(text) { q.value = text; reset() } function setPlatform(p) { platform.value = p; reset() } const hasMore = computed(() => !started || nextCursor.value !== null) const isEmpty = computed( () => loaded.value && !loading.value && cards.value.length === 0 && error.value === null ) return { cards, loading, error, q, platform, hasMore, isEmpty, loadMore, reset, setQuery, setPlatform, } })