fix(web): widget cache-busting, dead frontend code, and dependency pins (#656)

- Plugin-supplied widgets load as /static/plugin-widgets/...js?v=<plugin
  version>, so an update isn't hidden behind the year-long immutable cache.
- Fire-and-forget loadInstalledPlugins() calls catch the rejection it has
  already reported, so the global handler no longer adds a second toast.
- Timezone picker renders again when the General partial is re-injected.
- Remove dead code: executePluginAction's six plugin-id fallbacks and
  [DEBUG] logging, window.currentPluginConfig and every read of it, the
  file-upload JSON delete branch, unused PluginAPI / PluginInstallManager /
  PluginStateManager helpers, loadPluginWidgetsFromManifest, the stale
  install_manager.js and LEDVisibility fallbacks, error_handler.js's global
  escapeHtml, 13 unused CSS rules, and stale comments/no-op returns.
- pytz < 2027, psutil < 7 in requirements-test.txt, pytest-cov < 8.
- Pin anthropics/claude-code-action to the commit v1 resolves to.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Chuck
2026-09-28 10:40:52 -04:00
committed by GitHub
co-authored by Claude Opus 5.5
parent 65d82580bc
commit 0e9e2cabba
24 changed files with 109 additions and 579 deletions
@@ -31,8 +31,6 @@ const RequestThrottler = {
debugLog('[RequestThrottler] Creating new request for:', key);
// Create throttled request with abort support
let abortController = null;
const promise = new Promise((resolve, reject) => {
setTimeout(async () => {
try {
@@ -54,22 +52,8 @@ const RequestThrottler = {
this.pending.delete(key);
}
}, delay);
// Store abort controller if available
if (fn.abort && typeof fn.abort === 'function') {
abortController = fn.abort;
}
});
// Add abort method if available
if (abortController) {
promise.abort = () => {
debugLog('[RequestThrottler] Aborting request for:', key);
abortController.abort();
this.pending.delete(key);
};
}
this.pending.set(key, promise);
return promise;
},
@@ -83,18 +67,6 @@ const RequestThrottler = {
} else {
this.cache.clear();
}
},
/**
* Get statistics about pending requests and cache
*/
getStats() {
return {
pendingCount: this.pending.size,
cacheSize: this.cache.size,
pendingKeys: Array.from(this.pending.keys()),
cacheKeys: Array.from(this.cache.keys())
};
}
};
@@ -225,18 +197,6 @@ const PluginAPI = {
}
},
/**
* Batch multiple requests together for better performance
*
* @param {Array} requests - Array of {endpoint, method, data} objects
* @returns {Promise<Array>} Array of response data
*/
async batch(requests) {
return Promise.all(requests.map(req =>
this.request(req.endpoint, req.method || 'GET', req.data || null, false)
));
},
/**
* Clear API cache
*/
@@ -308,34 +268,6 @@ const PluginAPI = {
return await this.request(`/plugins/config/reset?plugin_id=${encodeURIComponent(pluginId)}`, 'POST');
},
/**
* Get plugin schema.
*
* @param {string} pluginId - Plugin identifier
* @returns {Promise<Object>} Plugin schema
*/
async getPluginSchema(pluginId) {
const response = await this.request(`/plugins/schema?plugin_id=${encodeURIComponent(pluginId)}`);
return response.data?.schema || null;
},
/**
* Install plugin from store.
*
* @param {string} pluginId - Plugin identifier
* @param {string} branch - Optional branch name to install from
* @returns {Promise<Object>} Response data
*/
async installPlugin(pluginId, branch = null) {
const data = {
plugin_id: pluginId
};
if (branch) {
data.branch = branch;
}
return await this.request('/plugins/install', 'POST', data);
},
/**
* Update plugin.
*
@@ -348,28 +280,6 @@ const PluginAPI = {
});
},
/**
* Uninstall plugin.
*
* @param {string} pluginId - Plugin identifier
* @returns {Promise<Object>} Response data
*/
async uninstallPlugin(pluginId) {
return await this.request('/plugins/uninstall', 'POST', {
plugin_id: pluginId
});
},
/**
* Get plugin store.
*
* @returns {Promise<Array>} List of available plugins
*/
async getPluginStore() {
const response = await this.request('/plugins/store/list');
return response.data || [];
},
/**
* Get plugin health.
*