From 68a0fe118266a277d434eff990fbd6bbe2c6fb18 Mon Sep 17 00:00:00 2001 From: Chuck <33324927+ChuckBuilds@users.noreply.github.com> Date: Mon, 30 Mar 2026 12:34:00 -0400 Subject: [PATCH] fix(web): resolve plugin settings tabs not loading (#301) * fix(web): resolve plugin settings tabs not loading due to enhancement race Two co-occurring bugs prevented plugin setting tabs from loading: 1. Both stub-to-full app() enhancement paths (tryEnhance and requestAnimationFrame) could fire independently, with the second overwriting installedPlugins back to [] after init() already fetched them. Added a guard flag (_appEnhanced) and runtime state preservation to prevent this race. 2. Plugin config x-init only loaded content if window.htmx was available at that exact moment, with no retry or fallback. Added retry loop (up to 3s) and fetch() fallback for resilience. Co-Authored-By: Claude Opus 4.6 (1M context) * fix(web): use runtime default tab and add Alpine.initTree to fetch fallback - Replace hard-coded 'overview' comparison with runtime defaultTab (isAPMode ? 'wifi' : 'overview') in both enhancement paths, so activeTab is preserved correctly in AP mode - Add Alpine.initTree(el) call in the plugin config fetch() fallback so Alpine directives in the injected HTML are initialized, matching the pattern used by loadOverviewDirect and loadWifiDirect Co-Authored-By: Claude Opus 4.6 (1M context) --------- Co-authored-by: Claude Opus 4.6 (1M context) --- web_interface/templates/v3/base.html | 60 ++++++++++++++++++++++++---- 1 file changed, 52 insertions(+), 8 deletions(-) diff --git a/web_interface/templates/v3/base.html b/web_interface/templates/v3/base.html index c0975c10..54f8354d 100644 --- a/web_interface/templates/v3/base.html +++ b/web_interface/templates/v3/base.html @@ -519,6 +519,9 @@ } }); + // Guard flag to prevent duplicate stub-to-full enhancement + window._appEnhanced = false; + // Define app() function early so Alpine can find it when it initializes // This is a complete implementation that will work immediately (function() { @@ -534,15 +537,25 @@ init() { // Try to enhance immediately with full implementation const tryEnhance = () => { + if (window._appEnhanced) return true; if (typeof window.app === 'function') { const fullApp = window.app(); // Check if this is the full implementation (has updatePluginTabs with proper implementation) if (fullApp && typeof fullApp.updatePluginTabs === 'function' && fullApp.updatePluginTabs.toString().includes('_doUpdatePluginTabs')) { - // Full implementation is available, copy all methods - // But preserve _initialized flag to prevent double init + window._appEnhanced = true; + // Preserve runtime state that should not be reset + const preservedPlugins = this.installedPlugins; + const preservedTab = this.activeTab; + const defaultTab = isAPMode ? 'wifi' : 'overview'; const wasInitialized = this._initialized; Object.assign(this, fullApp); - // Restore _initialized flag if it was set + // Restore runtime state if non-default + if (preservedPlugins && preservedPlugins.length > 0) { + this.installedPlugins = preservedPlugins; + } + if (preservedTab && preservedTab !== defaultTab) { + this.activeTab = preservedTab; + } if (wasInitialized) { this._initialized = wasInitialized; } @@ -1252,11 +1265,27 @@