fix: resolve infinite WebSocket reconnection loop

The ui_server proxies WebSocket connections. It was accepting the client's WebSocket connection (ws.onopen triggered on the client), but if it couldn't connect to the backend (or if the backend disconnected), it would drop the client connection right away (ws.onclose triggered).

The issue was that reconnectAttempts was being reset to 0 inside the ws.onopen handler. Because the connection was briefly succeeding before failing, the reconnectAttempts counter was resetting to 0 on every attempt, completely circumventing the exponential backoff mechanism and causing a tight reconnection loop.

Modified the WebSocket logic across all relevant UI components to delay resetting reconnectAttempts = 0. Instead of resetting immediately upon the TCP socket opening, it now safely waits until a valid JSON payload {"type": "connected"} is successfully received from the backend.
This commit is contained in:
Rodrigo Rodriguez (Pragmatismo) 2026-02-25 10:15:47 -03:00
parent bfc8f4da77
commit 7c1deca8ae
9 changed files with 53 additions and 19 deletions

View file

@ -1101,6 +1101,7 @@
const r = JSON.parse(e.data); const r = JSON.parse(e.data);
if (r.type === "connected") { if (r.type === "connected") {
console.log("WebSocket welcome message:", r); console.log("WebSocket welcome message:", r);
reconnectAttempts = 0;
return; return;
} }
if (r.bot_id) { if (r.bot_id) {
@ -1154,7 +1155,6 @@
ws.readyState, ws.readyState,
); );
updateConnectionStatus("connected"); updateConnectionStatus("connected");
reconnectAttempts = 0;
hasReceivedInitialMessage = false; hasReceivedInitialMessage = false;
}; };
ws.onclose = function (e) { ws.onclose = function (e) {

View file

@ -1322,7 +1322,6 @@
"WebSocket connected for attendant:", "WebSocket connected for attendant:",
currentAttendantId, currentAttendantId,
); );
reconnectAttempts = 0;
showToast( showToast(
"Connected to notification service", "Connected to notification service",
"success", "success",
@ -1367,6 +1366,7 @@
switch (msgType) { switch (msgType) {
case "connected": case "connected":
console.log("WebSocket connected:", data.message); console.log("WebSocket connected:", data.message);
reconnectAttempts = 0;
break; break;
case "new_conversation": case "new_conversation":
showToast("New conversation in queue", "info"); showToast("New conversation in queue", "info");

View file

@ -886,7 +886,6 @@
ws.onopen = function () { ws.onopen = function () {
clearTimeout(connectionTimeout); clearTimeout(connectionTimeout);
console.log("WebSocket connected to:", url); console.log("WebSocket connected to:", url);
reconnectAttempts = 0;
disconnectNotified = false; disconnectNotified = false;
updateConnectionStatus("connected"); updateConnectionStatus("connected");
}; };
@ -897,7 +896,10 @@
console.log("Chat WebSocket received:", data); console.log("Chat WebSocket received:", data);
// Ignore connection confirmation // Ignore connection confirmation
if (data.type === "connected") return; if (data.type === "connected") {
reconnectAttempts = 0;
return;
}
// Process system events (theme changes, etc) // Process system events (theme changes, etc)
if (data.event) { if (data.event) {

View file

@ -14,6 +14,7 @@
<!-- Local JS requirements per AGENTS.md / UI.md --> <!-- Local JS requirements per AGENTS.md / UI.md -->
<script src="/suite/js/vendor/htmx.min.js"></script> <script src="/suite/js/vendor/htmx.min.js"></script>
<script src="/suite/js/window-manager.js"></script> <script src="/suite/js/window-manager.js"></script>
<script src="/suite/js/theme-manager.js"></script>
<style> <style>
* { margin: 0; padding: 0; box-sizing: border-box; } * { margin: 0; padding: 0; box-sizing: border-box; }
@ -214,10 +215,13 @@
<div id="taskbar-apps"> <div id="taskbar-apps">
<!-- Taskbar items populated automatically by window-manager.js --> <!-- Taskbar items populated automatically by window-manager.js -->
</div> </div>
<div class="toolbar-time"> <div class="toolbar-time" style="display: flex; align-items: center; gap: 15px;">
<div id="themeSelectorContainer"></div>
<div style="text-align: right;">
<div id="clock-time">00:00</div> <div id="clock-time">00:00</div>
<div id="clock-date">01/01/2026</div> <div id="clock-date">01/01/2026</div>
</div> </div>
</div>
</footer> </footer>
</div> </div>
</div> </div>
@ -231,6 +235,11 @@
} else { } else {
console.error("WindowManager class not loaded from window-manager.js"); console.error("WindowManager class not loaded from window-manager.js");
} }
// Initialize ThemeManager
if (typeof window.ThemeManager !== 'undefined') {
window.ThemeManager.init();
}
}); });
// Listen to HTMX afterRequest event // Listen to HTMX afterRequest event
@ -248,6 +257,15 @@
window.wm.open(appId, title, htmlContent); window.wm.open(appId, title, htmlContent);
} }
} }
// Ensure Theme dropdown is re-injected if wiped
if (window.ThemeManager) {
const container = document.getElementById('themeSelectorContainer');
if (container && !container.hasChildNodes()) {
// Quick and dirty way to re-init
window.ThemeManager.init();
}
}
}); });
// Simple Clock implementation matching the screenshot bottom right corner // Simple Clock implementation matching the screenshot bottom right corner

View file

@ -192,7 +192,6 @@
document.body.addEventListener("htmx:wsOpen", () => { document.body.addEventListener("htmx:wsOpen", () => {
updateConnectionStatus("connected"); updateConnectionStatus("connected");
reconnectAttempts = 0;
}); });
document.body.addEventListener("htmx:wsClose", () => { document.body.addEventListener("htmx:wsClose", () => {
@ -205,6 +204,10 @@
function handleWebSocketMessage(message) { function handleWebSocketMessage(message) {
const messageType = message.type || message.event; const messageType = message.type || message.event;
if (messageType === "connected") {
reconnectAttempts = 0;
}
// Debug logging // Debug logging
console.log("handleWebSocketMessage called with:", { messageType, message }); console.log("handleWebSocketMessage called with:", { messageType, message });

View file

@ -173,7 +173,8 @@ const ThemeManager = (() => {
} }
function updateDropdown() { function updateDropdown() {
// Dropdown removed const select = document.getElementById("themeDropdown");
if (select) select.value = currentThemeId;
} }
function createDropdown() { function createDropdown() {
@ -214,9 +215,12 @@ const ThemeManager = (() => {
currentThemeId = saved; currentThemeId = saved;
loadTheme(saved); loadTheme(saved);
// Dropdown injection removed // Dropdown injection restored for the window manager
// const container = document.getElementById("themeSelectorContainer"); const container = document.getElementById("themeSelectorContainer");
// if (container) container.appendChild(createDropdown()); if (container) {
container.innerHTML = '';
container.appendChild(createDropdown());
}
console.log("✓ Theme Manager initialized"); console.log("✓ Theme Manager initialized");
} }

View file

@ -875,7 +875,6 @@
ws.onopen = function () { ws.onopen = function () {
console.log("WebSocket connected"); console.log("WebSocket connected");
reconnectAttempts = 0;
disconnectNotified = false; disconnectNotified = false;
updateConnectionStatus("connected"); updateConnectionStatus("connected");
}; };
@ -886,7 +885,10 @@
console.log("Chat WebSocket received:", data); console.log("Chat WebSocket received:", data);
// Ignore connection confirmation // Ignore connection confirmation
if (data.type === "connected") return; if (data.type === "connected") {
reconnectAttempts = 0;
return;
}
// Process system events (theme changes, etc) // Process system events (theme changes, etc)
if (data.event) { if (data.event) {

View file

@ -177,10 +177,13 @@
<div id="taskbar-apps"> <div id="taskbar-apps">
<!-- Taskbar items populated automatically by window-manager.js --> <!-- Taskbar items populated automatically by window-manager.js -->
</div> </div>
<div class="toolbar-time"> <div class="toolbar-time" style="display: flex; align-items: center; gap: 15px;">
<div id="themeSelectorContainer"></div>
<div style="text-align: right;">
<div id="clock-time">00:00</div> <div id="clock-time">00:00</div>
<div id="clock-date">01/01/2026</div> <div id="clock-date">01/01/2026</div>
</div> </div>
</div>
</footer> </footer>
</div> </div>
</div> </div>

View file

@ -875,7 +875,6 @@
ws.onopen = function () { ws.onopen = function () {
console.log("WebSocket connected"); console.log("WebSocket connected");
reconnectAttempts = 0;
disconnectNotified = false; disconnectNotified = false;
updateConnectionStatus("connected"); updateConnectionStatus("connected");
}; };
@ -886,7 +885,10 @@
console.log("Chat WebSocket received:", data); console.log("Chat WebSocket received:", data);
// Ignore connection confirmation // Ignore connection confirmation
if (data.type === "connected") return; if (data.type === "connected") {
reconnectAttempts = 0;
return;
}
// Process system events (theme changes, etc) // Process system events (theme changes, etc)
if (data.event) { if (data.event) {