Revert "Prevent WebUI extension loading stalls"
This reverts commit bf2046ff8704b4f70065226b96059795ae257fe7.
Alessandro committed
Jul 18, 2026 at 13:32 UTC
9e2a1b63243a015032d40c96387c3b7f4e31be42
4 files changed
+14
-43
tests/test_webui_loader_static.py
deleted
-19
@@ -1,19 +0,0 @@
1
-from pathlib import Path
2
-
3
-
4
-PROJECT_ROOT = Path(__file__).resolve().parents[1]
5
-
6
-
7
-def test_extension_discovery_requests_are_serialized() -> None:
8
- source = (PROJECT_ROOT / "webui/js/extensions.js").read_text(encoding="utf-8")
9
-
10
- assert "let extensionRequestQueue = Promise.resolve();" in source
11
- assert "extensionRequestQueue = request.catch(() => {});" in source
12
- assert source.count("await requestExtensionPaths(") == 2
13
-
14
-
15
-def test_component_placeholder_is_removed_after_failure() -> None:
16
- source = (PROJECT_ROOT / "webui/js/components.js").read_text(encoding="utf-8")
17
- finally_block = source.rsplit("} finally {", maxsplit=1)[1]
18
-
19
- assert "targetElement.querySelector(':scope > .loading')?.remove();" in finally_block
webui/js/AGENTS.md
-2
@@ -37,10 +37,8 @@
37
- `scrollModal(id)` scrolls inside the top modal's `.modal-scroll`.
38
- Keep extension loader cache keys and extension point names stable for plugins.
39
- HTML extension loading turns discovered HTML files into `<x-component>` tags; JavaScript extensions must export a default function.
40
-- Serialize frontend extension-discovery API requests so a page with many extension points cannot exhaust embedded Chromium request resources.
40
- `<x-component>` loading must process component `style`, `script`, and stylesheet-link assets only once, even when a component keeps its scoped `<style>` inside `<body>`.
41
- Every `<x-component>` instance must await cached module-load promises before markup is appended so Alpine bindings only run after imported stores exist.
43
-- Every `<x-component>` loading placeholder must be removed after success or failure; a failed asset must never leave a perpetual shimmer.
42
- Frontend extension hooks such as `confirm_dialog_after_render` and `get_tool_message_handler` must preserve their mutable context contracts.
43
- Sanitize or safely render user/model-provided HTML and markdown.
44
- Convert standard TeX delimiters before Markdown parsing without touching inline or fenced code. Keep thought-card math rendering local to the agent-message handler rather than adding math flags to generic process-step or key/value rendering.
webui/js/components.js
+6
-1
@@ -170,6 +170,12 @@ export async function importComponent(path, targetElement) {
170
targetElement.appendChild(deferred);
171
}
172
173
+ // Remove loading indicator
174
+ const loadingEl = targetElement.querySelector(':scope > .loading');
175
+ if (loadingEl) {
176
+ targetElement.removeChild(loadingEl);
177
+ }
178
+
179
// // Load any nested components
180
// await loadComponents([targetElement]);
181
@@ -179,7 +185,6 @@ export async function importComponent(path, targetElement) {
185
console.error("Error importing component:", error);
186
throw error;
187
} finally {
182
- targetElement.querySelector(':scope > .loading')?.remove();
188
// Release the lock when done, regardless of success or failure
189
importLocks.delete(lockKey);
190
}
webui/js/extensions.js
+8
-21
@@ -20,7 +20,6 @@ import * as cache from "./cache.js";
20
21
const JS_CACHE_AREA = "frontend_extensions_js(extensions)(plugins)";
22
const HTML_CACHE_AREA = "frontend_extensions_html(extensions)(plugins)";
23
-let extensionRequestQueue = Promise.resolve();
23
24
export const API_EXTENSION_EXCLUDED_ENDPOINTS = new Set([
25
"/api/load_webui_extensions",
@@ -31,17 +30,6 @@ export function clearCache() {
30
cache.clear(HTML_CACHE_AREA);
31
}
32
34
-function requestExtensionPaths(extensionPoint, filters) {
35
- const request = extensionRequestQueue.then(() =>
36
- api.callJsonApi(`/api/load_webui_extensions`, {
37
- extension_point: extensionPoint,
38
- filters,
39
- }),
40
- );
41
- extensionRequestQueue = request.catch(() => {});
42
- return request;
43
-}
44
-
33
/**
34
* Call all JS extensions for a given extension point.
35
*
@@ -72,10 +60,10 @@ export async function loadJsExtensions(extensionPoint) {
60
if (cached != null) return cached;
61
62
/** @type {LoadWebuiExtensionsResponse} */
75
- const response = await requestExtensionPaths(extensionPoint, [
76
- "*.js",
77
- "*.mjs",
78
- ]);
63
+ const response = await api.callJsonApi(`/api/load_webui_extensions`, {
64
+ extension_point: extensionPoint,
65
+ filters: ["*.js", "*.mjs"],
66
+ });
67
/** @type {JsExtensionImport[]} */
68
const imports = await Promise.all(
69
response.extensions.map(async (path) => ({
@@ -179,11 +167,10 @@ export async function importHtmlExtensions(extensionPoint, targetElement) {
167
}
168
169
/** @type {LoadWebuiExtensionsResponse} */
182
- const response = await requestExtensionPaths(extensionPoint, [
183
- "*.html",
184
- "*.htm",
185
- "*.xhtml",
186
- ]);
170
+ const response = await api.callJsonApi(`/api/load_webui_extensions`, {
171
+ extension_point: extensionPoint,
172
+ filters: ["*.html", "*.htm", "*.xhtml"],
173
+ });
174
let combinedHTML = "";
175
for (const extension of response.extensions) {
176
const path = normalizePath(extension);