From 82a183c62344dc0999082a2b219898a937f06d43 Mon Sep 17 00:00:00 2001 From: Chuck Date: Thu, 30 Apr 2026 16:11:05 -0400 Subject: [PATCH] fix(plugins): fix async race in refreshPlugins; use cache TTL to gate re-swap metadata fetch refreshPlugins() called searchPluginStore(true) and showNotification() immediately after refreshInstalledPlugins() without awaiting the returned Promise, so window.installedPlugins could still be stale when the store rendered its Installed/Reinstall badges. Chain .then() so both run only after the fetch completes. In initializePlugins(), the re-swap path always passed fetchCommitInfo=false to searchPluginStore, skipping GitHub metadata even when the 5-minute cache TTL had expired. Add storeCacheExpired() helper and compute isReswapWarm = _reswap && !storeCacheExpired() so fresh metadata is fetched whenever the cache is cold, regardless of whether the render is a first load or a tab re-swap. Co-Authored-By: Claude Sonnet 4.6 --- web_interface/static/v3/plugins_manager.js | 24 ++++++++++++++-------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/web_interface/static/v3/plugins_manager.js b/web_interface/static/v3/plugins_manager.js index 66ef498a..0eeb9b79 100644 --- a/web_interface/static/v3/plugins_manager.js +++ b/web_interface/static/v3/plugins_manager.js @@ -898,6 +898,10 @@ window.currentPluginConfig = null; const CACHE_DURATION = 5 * 60 * 1000; // 5 minutes in milliseconds let storeFilteredList = []; + function storeCacheExpired() { + return !cacheTimestamp || (Date.now() - cacheTimestamp >= CACHE_DURATION); + } + // ── Plugin Store Filter State ─────────────────────────────────────────── const storeFilterState = { sort: safeLocalStorage.getItem('storeSort') || 'a-z', @@ -1214,12 +1218,13 @@ function initializePlugins() { } // Load both installed plugins and plugin store. - // On HTMX re-swaps use cached store data (fetchCommitInfo=false) to avoid - // re-hitting GitHub on every tab switch; only fetch fresh on first load. - const isReswap = !!window.pluginManager._reswap; + // On HTMX re-swaps with a still-warm cache, skip GitHub metadata to avoid + // re-hitting the API on every tab switch. If the cache TTL has expired even + // during a re-swap, fetch fresh data including GitHub commit/version info. + const isReswapWarm = !!window.pluginManager._reswap && !storeCacheExpired(); window.pluginManager._reswap = false; loadInstalledPlugins(); - searchPluginStore(!isReswap); + searchPluginStore(!isReswapWarm); // Setup search functionality (with guard against duplicate listeners) const searchInput = document.getElementById('plugin-search'); @@ -5133,10 +5138,13 @@ function refreshPlugins() { pluginStoreCache = null; cacheTimestamp = null; - refreshInstalledPlugins(); // invalidates cache before fetching - // Fetch latest metadata from GitHub when refreshing - searchPluginStore(true); - showNotification('Plugins refreshed with latest metadata from GitHub', 'success'); + // refreshInstalledPlugins() is async (returns a Promise via loadInstalledPlugins). + // Only search the store and notify after window.installedPlugins is updated so + // that Installed/Reinstall badges reflect the freshly fetched state. + refreshInstalledPlugins().then(() => { + searchPluginStore(true); + showNotification('Plugins refreshed with latest metadata from GitHub', 'success'); + }); } function restartDisplay() {