-
-
- File Browser
-
+
+
+
+ File Browser
+
+
+
Date: Tue, 29 Sep 2026 17:37:35 -0300
Subject: [PATCH 2/3] fix(webui): await refreshed file search
---
tests/webui_file_browser_state.test.cjs | 22 +++-
web_ui/static/js/app.js | 132 ++++++++++++++----------
2 files changed, 97 insertions(+), 57 deletions(-)
diff --git a/tests/webui_file_browser_state.test.cjs b/tests/webui_file_browser_state.test.cjs
index efb72d4b3..bd8f4a233 100644
--- a/tests/webui_file_browser_state.test.cjs
+++ b/tests/webui_file_browser_state.test.cjs
@@ -130,13 +130,28 @@ test("file-browser persistence, restoration, refresh and execution outcomes", as
context.fileBrowserRefreshing = false;
context.setFileBrowserRefreshing = (value) => refreshStates.push(value);
context.fileBrowserSearchQuery = { current: "new download" };
- context.handleFileBrowserSearch = (query) => manualSearches.push(query);
+ let finishManualSearch;
+ let manualSearchStarted;
+ const searchStarted = new Promise((resolve) => {
+ manualSearchStarted = resolve;
+ });
+ context.handleFileBrowserSearch = (query) => {
+ manualSearches.push(query);
+ manualSearchStarted();
+ return new Promise((resolve) => {
+ finishManualSearch = resolve;
+ });
+ };
load("refreshFileBrowser");
requests.length = 0;
- await context.refreshFileBrowser();
- assert.deepEqual(refreshStates, [true, false]);
+ const manualRefresh = context.refreshFileBrowser();
+ await searchStarted;
+ assert.deepEqual(refreshStates, [true]);
assert.deepEqual(requests, Object.keys(responses));
assert.deepEqual(manualSearches, ["new download"]);
+ finishManualSearch();
+ await manualRefresh;
+ assert.deepEqual(refreshStates, [true, false]);
let delayResolve;
let delay;
@@ -168,6 +183,7 @@ test("file-browser persistence, restoration, refresh and execution outcomes", as
assert.equal(searches.length, 1);
context.fileBrowserSearchTimer = { current: null };
+ context.fileBrowserSearchCompletion = { current: null };
context.fileBrowserSearchId = { current: 0 };
context.setFileBrowserSearch = () => {};
let searchLoading = false;
diff --git a/web_ui/static/js/app.js b/web_ui/static/js/app.js
index 349194de5..1c8c37770 100644
--- a/web_ui/static/js/app.js
+++ b/web_ui/static/js/app.js
@@ -2165,6 +2165,7 @@ function AudionutsUAGUI() {
useState(false);
const [fileBrowserRefreshing, setFileBrowserRefreshing] = useState(false);
const fileBrowserSearchTimer = useRef(null);
+ const fileBrowserSearchCompletion = useRef(null);
const fileBrowserSearchQuery = useRef("");
const fileBrowserSearchId = useRef(0);
@@ -3377,13 +3378,14 @@ function AudionutsUAGUI() {
}
};
+ /** Reload visible file-browser data without resetting navigation state. */
const refreshFileBrowser = async () => {
if (fileBrowserRefreshing) return;
setFileBrowserRefreshing(true);
try {
await loadBrowseRoots();
if (fileBrowserSearchQuery.current) {
- handleFileBrowserSearch(fileBrowserSearchQuery.current);
+ await handleFileBrowserSearch(fileBrowserSearchQuery.current);
}
} finally {
setFileBrowserRefreshing(false);
@@ -3552,6 +3554,8 @@ function AudionutsUAGUI() {
if (fileBrowserSearchTimer.current) {
clearTimeout(fileBrowserSearchTimer.current);
}
+ fileBrowserSearchCompletion.current?.();
+ fileBrowserSearchCompletion.current = null;
};
}, []);
@@ -4093,68 +4097,88 @@ function AudionutsUAGUI() {
// File Browser search
const handleFileBrowserSearch = (value, signal) => {
- if (signal?.aborted) return;
- const searchId = ++fileBrowserSearchId.current;
- setFileBrowserSearch(value);
- const searchQuery = value.trim();
- fileBrowserSearchQuery.current = searchQuery;
+ if (signal?.aborted) return Promise.resolve();
+ fileBrowserSearchCompletion.current?.();
if (fileBrowserSearchTimer.current) {
clearTimeout(fileBrowserSearchTimer.current);
}
- if (!searchQuery) {
- setFileBrowserSearchResults(null);
- setFileBrowserSearchLoading(false);
- return;
- }
- setFileBrowserSearchLoading(true);
- const onAbort = () => {
- if (fileBrowserSearchId.current === searchId) {
- clearTimeout(fileBrowserSearchTimer.current);
+ return new Promise((resolve) => {
+ const searchId = ++fileBrowserSearchId.current;
+ let settled = false;
+ const settle = () => {
+ if (settled) return;
+ settled = true;
+ if (fileBrowserSearchCompletion.current === settle) {
+ fileBrowserSearchCompletion.current = null;
+ }
+ resolve();
+ };
+ fileBrowserSearchCompletion.current = settle;
+ setFileBrowserSearch(value);
+ const searchQuery = value.trim();
+ fileBrowserSearchQuery.current = searchQuery;
+ if (!searchQuery) {
+ setFileBrowserSearchResults(null);
setFileBrowserSearchLoading(false);
+ settle();
+ return;
}
- };
- signal?.addEventListener("abort", onAbort, { once: true });
- fileBrowserSearchTimer.current = setTimeout(async () => {
- if (signal?.aborted) return;
- try {
- const response = await apiFetch(
- `${API_BASE}/browse_search?q=${encodeURIComponent(searchQuery)}`,
- { signal },
- );
- if (signal?.aborted) return;
- if (!response.ok) {
- throw new Error(`Search request failed (${response.status})`);
- }
- const data = await response.json();
- if (signal?.aborted) return;
- // Early return if the search has changed since this request
- if (fileBrowserSearchId.current !== searchId) return;
- if (data.success) {
- setFileBrowserSearchResults(data);
- } else {
- setFileBrowserSearchResults({
- items: [],
- query: searchQuery,
- count: 0,
- });
- }
- } catch (error) {
- if (signal?.aborted) return;
- console.error("File browser search failed:", error);
+ setFileBrowserSearchLoading(true);
+ const onAbort = () => {
if (fileBrowserSearchId.current === searchId) {
- setFileBrowserSearchResults({
- items: [],
- query: searchQuery,
- count: 0,
- });
+ clearTimeout(fileBrowserSearchTimer.current);
+ setFileBrowserSearchLoading(false);
}
- } finally {
signal?.removeEventListener("abort", onAbort);
- if (!signal?.aborted && fileBrowserSearchId.current === searchId) {
- setFileBrowserSearchLoading(false);
+ settle();
+ };
+ signal?.addEventListener("abort", onAbort, { once: true });
+ fileBrowserSearchTimer.current = setTimeout(async () => {
+ if (signal?.aborted) {
+ settle();
+ return;
}
- }
- }, 300); //300ms debounce so we dont spam requests for every keystroke
+ try {
+ const response = await apiFetch(
+ `${API_BASE}/browse_search?q=${encodeURIComponent(searchQuery)}`,
+ { signal },
+ );
+ if (signal?.aborted) return;
+ if (!response.ok) {
+ throw new Error(`Search request failed (${response.status})`);
+ }
+ const data = await response.json();
+ if (signal?.aborted) return;
+ // Early return if the search has changed since this request
+ if (fileBrowserSearchId.current !== searchId) return;
+ if (data.success) {
+ setFileBrowserSearchResults(data);
+ } else {
+ setFileBrowserSearchResults({
+ items: [],
+ query: searchQuery,
+ count: 0,
+ });
+ }
+ } catch (error) {
+ if (signal?.aborted) return;
+ console.error("File browser search failed:", error);
+ if (fileBrowserSearchId.current === searchId) {
+ setFileBrowserSearchResults({
+ items: [],
+ query: searchQuery,
+ count: 0,
+ });
+ }
+ } finally {
+ signal?.removeEventListener("abort", onAbort);
+ if (!signal?.aborted && fileBrowserSearchId.current === searchId) {
+ setFileBrowserSearchLoading(false);
+ }
+ settle();
+ }
+ }, 300); //300ms debounce so we dont spam requests for every keystroke
+ });
};
const refreshFileBrowserAfterUpload = async (signal) => {
From fc22596d48a850d300b8ce012d515620400ca9f0 Mon Sep 17 00:00:00 2001
From: wastaken7 <46319635+wastaken7@users.noreply.github.com>
Date: Thu, 1 Oct 2026 11:54:59 -0300
Subject: [PATCH 3/3] Update web_ui/static/js/app.js
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
---
web_ui/static/js/app.js | 1 +
1 file changed, 1 insertion(+)
diff --git a/web_ui/static/js/app.js b/web_ui/static/js/app.js
index 1c8c37770..74862b9c0 100644
--- a/web_ui/static/js/app.js
+++ b/web_ui/static/js/app.js
@@ -3383,6 +3383,7 @@ function AudionutsUAGUI() {
if (fileBrowserRefreshing) return;
setFileBrowserRefreshing(true);
try {
+ setFileBrowserRestoring(true);
await loadBrowseRoots();
if (fileBrowserSearchQuery.current) {
await handleFileBrowserSearch(fileBrowserSearchQuery.current);