From 72167524debe853e15360a62515178ab44bb2352 Mon Sep 17 00:00:00 2001 From: mvdbeek Date: Mon, 23 Mar 2026 12:07:22 +0100 Subject: [PATCH] Fix race condition in history list when rapidly changing search filters When a search reset and a new search triggered concurrent load() calls, a stale response from the reset could overwrite the correct search results. Use a generation counter to discard responses from superseded loads. Co-Authored-By: Claude Opus 4.6 (1M context) --- client/src/components/History/HistoryList.vue | 40 +++++++++++-------- 1 file changed, 24 insertions(+), 16 deletions(-) diff --git a/client/src/components/History/HistoryList.vue b/client/src/components/History/HistoryList.vue index 6bc219d4e9b..8c6cf1d8f8e 100644 --- a/client/src/components/History/HistoryList.vue +++ b/client/src/components/History/HistoryList.vue @@ -92,6 +92,7 @@ const limit = ref(24); const offset = ref(0); const loading = ref(true); const overlay = ref(false); +let loadGeneration = 0; const filterText = ref(""); const totalHistories = ref(0); const showAdvanced = ref(false); @@ -219,6 +220,8 @@ function onToggleDeleted() { * @param {boolean} silent - Whether to skip loading indicators */ async function load(overlayLoading: boolean = false, silent: boolean = false) { + const thisGeneration = ++loadGeneration; + if (!silent) { if (overlayLoading) { overlay.value = true; @@ -242,32 +245,37 @@ async function load(overlayLoading: boolean = false, silent: boolean = false) { }; try { + let data; + let total; + if (myView.value) { - const { data, total } = await getMyHistories(options); - - historiesLoaded.value = data; - totalHistories.value = total; + ({ data, total } = await getMyHistories(options)); } else if (sharedView.value) { - const { data, total } = await getSharedHistories(options); - - historiesLoaded.value = data; - totalHistories.value = total; + ({ data, total } = await getSharedHistories(options)); } else if (publishedView.value) { - const { data, total } = await getPublishedHistories(options); - - historiesLoaded.value = data; - totalHistories.value = total; + ({ data, total } = await getPublishedHistories(options)); } else if (archivedView.value) { - const { data, total } = await getArchivedHistories(options); + ({ data, total } = await getArchivedHistories(options)); + } + if (thisGeneration !== loadGeneration) { + return; + } + + if (data !== undefined) { historiesLoaded.value = data; - totalHistories.value = total; + totalHistories.value = total!; } } catch (error) { + if (thisGeneration !== loadGeneration) { + return; + } Toast.error(`Failed to load histories: ${errorMessageAsString(error)}`); } finally { - loading.value = false; - overlay.value = false; + if (thisGeneration === loadGeneration) { + loading.value = false; + overlay.value = false; + } } }