diff --git a/web_interface/app.py b/web_interface/app.py
index 42568ce0..cf2a58a3 100644
--- a/web_interface/app.py
+++ b/web_interface/app.py
@@ -726,4 +726,6 @@ def check_health_monitor():
_threading.Thread(target=_run_startup_reconciliation, daemon=True).start()
if __name__ == '__main__':
- app.run(host='0.0.0.0', port=5000, debug=True)
+ # threaded=True is Flask's default since 1.0 but stated explicitly so that
+ # long-lived /api/v3/stream/* SSE connections don't starve other requests.
+ app.run(host='0.0.0.0', port=5000, debug=True, threaded=True)
diff --git a/web_interface/start.py b/web_interface/start.py
index c2562954..885bd136 100644
--- a/web_interface/start.py
+++ b/web_interface/start.py
@@ -120,7 +120,11 @@ def main():
# Run the web server with error handling for client disconnections
try:
- app.run(host='0.0.0.0', port=5000, debug=False)
+ # threaded=True is Flask's default since 1.0, but set it explicitly
+ # so it's self-documenting: the two /api/v3/stream/* SSE endpoints
+ # hold long-lived connections and would starve other requests under
+ # a single-threaded server.
+ app.run(host='0.0.0.0', port=5000, debug=False, threaded=True)
except (OSError, BrokenPipeError) as e:
# Suppress non-critical socket errors (client disconnections)
if isinstance(e, OSError) and e.errno in (113, 32, 104): # No route to host, Broken pipe, Connection reset
diff --git a/web_interface/static/v3/plugins_manager.js b/web_interface/static/v3/plugins_manager.js
index e75aee32..0f15b33c 100644
--- a/web_interface/static/v3/plugins_manager.js
+++ b/web_interface/static/v3/plugins_manager.js
@@ -7161,6 +7161,13 @@ window.getSchemaProperty = getSchemaProperty;
window.escapeHtml = escapeHtml;
window.escapeAttribute = escapeAttribute;
+// Expose GitHub install handlers. These must be assigned inside the IIFE —
+// from outside the IIFE, `typeof attachInstallButtonHandler` evaluates to
+// 'undefined' and the fallback path at the bottom of this file fires a
+// [FALLBACK] attachInstallButtonHandler not available on window warning.
+window.attachInstallButtonHandler = attachInstallButtonHandler;
+window.setupGitHubInstallHandlers = setupGitHubInstallHandlers;
+
})(); // End IIFE
// Functions to handle array-of-objects
@@ -7390,16 +7397,8 @@ if (typeof loadInstalledPlugins !== 'undefined') {
if (typeof renderInstalledPlugins !== 'undefined') {
window.renderInstalledPlugins = renderInstalledPlugins;
}
-// Expose GitHub install handlers for debugging and manual testing
-if (typeof setupGitHubInstallHandlers !== 'undefined') {
- window.setupGitHubInstallHandlers = setupGitHubInstallHandlers;
- console.log('[GLOBAL] setupGitHubInstallHandlers exposed to window');
-}
-if (typeof attachInstallButtonHandler !== 'undefined') {
- window.attachInstallButtonHandler = attachInstallButtonHandler;
- console.log('[GLOBAL] attachInstallButtonHandler exposed to window');
-}
-// searchPluginStore is now exposed inside the IIFE after its definition
+// GitHub install handlers are now exposed inside the IIFE (see above).
+// searchPluginStore is also exposed inside the IIFE after its definition.
// Verify critical functions are available
if (_PLUGIN_DEBUG_EARLY) {
diff --git a/web_interface/templates/v3/base.html b/web_interface/templates/v3/base.html
index e3da43d3..e4dbf5ff 100644
--- a/web_interface/templates/v3/base.html
+++ b/web_interface/templates/v3/base.html
@@ -786,56 +786,25 @@
})();
-
-
+
+