Await cached component module imports

Ensure x-component loading awaits cached dynamic import promises before appending deferred markup, matching first-load behavior and preventing Alpine bindings from running before imported stores register. Document the loader contract in the WebUI JS DOX notes.

Alessandro committed Jul 8, 2026 at 20:53 UTC a7255d97734abe511f04f6ffa4b6aa076270d2bd
2 files changed +3 -2
webui/js/AGENTS.md
+1
@@ -37,6 +37,7 @@
37 - Keep extension loader cache keys and extension point names stable for plugins.
38 - HTML extension loading turns discovered HTML files into `<x-component>` tags; JavaScript extensions must export a default function.
39 - `<x-component>` loading must process component `style`, `script`, and stylesheet-link assets only once, even when a component keeps its scoped `<style>` inside `<body>`.
40 +- Every `<x-component>` instance must await cached module-load promises before markup is appended so Alpine bindings only run after imported stores exist.
41 - Frontend extension hooks such as `confirm_dialog_after_render` and `get_tool_message_handler` must preserve their mutable context contracts.
42 - Sanitize or safely render user/model-provided HTML and markdown.
43 - Do not expose secrets in localStorage, console logs, URLs, or WebSocket payloads.
webui/js/components.js
+2 -2
@@ -79,8 +79,8 @@ export async function importComponent(path, targetElement) {
79 if (!componentCache[resolvedUrl]) {
80 const modulePromise = import(resolvedUrl);
81 componentCache[resolvedUrl] = modulePromise;
82 - loadPromises.push(modulePromise);
82 }
83 + loadPromises.push(componentCache[resolvedUrl]);
84 } else {
85 const virtualUrl = `${componentUrl.replaceAll(
86 "/",
@@ -122,8 +122,8 @@ export async function importComponent(path, targetElement) {
122 .finally(() => URL.revokeObjectURL(blobUrl));
123
124 componentCache[virtualUrl] = modulePromise;
125 - loadPromises.push(modulePromise);
125 }
126 + loadPromises.push(componentCache[virtualUrl]);
127 }
128 } else {
129 // Non-module script