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(),