fix: defer DOM insertion until module imports resolve in component loader

Body nodes (containing Alpine x-data directives) were appended to the DOM immediately during the import loop, while <script type="module"> imports were awaited afterward. Alpine's MutationObserver would evaluate x-data expressions before the module had registered its store, causing "Cannot convert undefined or null to object" errors. Collect body nodes into a buffer and append them only after Promise.all(loadPromises) resolves, ensuring stores are registered before Alpine processes the DOM.

Alessandro committed Mar 17, 2026 at 16:33 UTC 37d94433246e3e13908081b07b595e117ad51aba
1 file changed +6 -2
webui/js/components.js
+6 -2
@@ -57,6 +57,7 @@ export async function importComponent(path, targetElement) {
57 ];
58
59 const loadPromises = [];
60 + const deferredNodes = [];
61 let blobCounter = 0;
62
63 for (const node of allNodes) {
@@ -156,14 +157,17 @@ export async function importComponent(path, targetElement) {
157
158 targetElement.appendChild(clone);
159 } else {
159 - const clone = node.cloneNode(true);
160 - targetElement.appendChild(clone);
160 + deferredNodes.push(node.cloneNode(true));
161 }
162 }
163
164 // Wait for all tracked external scripts/styles to finish loading
165 await Promise.all(loadPromises);
166
167 + for (const deferred of deferredNodes) {
168 + targetElement.appendChild(deferred);
169 + }
170 +
171 // Remove loading indicator
172 const loadingEl = targetElement.querySelector(':scope > .loading');
173 if (loadingEl) {