From 221640582185c9b14d46873ef1b57cf639c187a8 Mon Sep 17 00:00:00 2001 From: chengyongru Date: Thu, 18 Jun 2026 18:30:25 +0800 Subject: [PATCH] fix: avoid stuck Apps loading during catalog refresh maintainer edit: Empty cache-only CLI Apps payloads should still update the UI while the background catalog refresh is pending, otherwise cold catalog failures leave Settings stuck on the spinner. --- .../src/components/settings/SettingsView.tsx | 1 - webui/src/tests/settings-view.test.tsx | 27 +++++++++++++++++++ 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/webui/src/components/settings/SettingsView.tsx b/webui/src/components/settings/SettingsView.tsx index d7d411c6..f94d2645 100644 --- a/webui/src/components/settings/SettingsView.tsx +++ b/webui/src/components/settings/SettingsView.tsx @@ -703,7 +703,6 @@ export function SettingsView({ if (cancelled) return; if (payload.catalog_refresh_pending) { retry = window.setTimeout(() => loadCliApps(false), 2000); - if (payload.apps.length === 0) return; } setCliApps(payload); setCliAppsError(null); diff --git a/webui/src/tests/settings-view.test.tsx b/webui/src/tests/settings-view.test.tsx index 8545f521..8522b59c 100644 --- a/webui/src/tests/settings-view.test.tsx +++ b/webui/src/tests/settings-view.test.tsx @@ -286,6 +286,33 @@ describe("SettingsView Apps catalog", () => { await waitFor(() => expect(onSettingsChange).toHaveBeenCalledWith(payload)); }); + it("does not keep Apps loading while an empty CLI catalog refresh is pending", async () => { + vi.stubGlobal( + "fetch", + vi.fn(async (input: RequestInfo | URL) => { + const url = String(input); + if (url === "/api/settings") return jsonResponse(settingsPayload()); + if (url === "/api/settings/cli-apps") { + return jsonResponse({ + apps: [], + installed_count: 0, + catalog_updated_at: null, + catalog_refresh_pending: true, + }); + } + if (url === "/api/settings/mcp-presets") { + return jsonResponse({ presets: [], installed_count: 0 }); + } + return { ok: false, status: 404, json: async () => ({}) } as Response; + }), + ); + + renderSettingsView(); + + expect(await screen.findByText("No apps match this filter.")).toBeInTheDocument(); + expect(screen.queryByText("Loading Apps...")).not.toBeInTheDocument(); + }); + it("shows token activity on the overview", async () => { const payload: SettingsPayload = { ...settingsPayload(),