botui/ui/suite/partials/desktop-inner.html
Rodrigo Rodriguez (Pragmatismo) 7c1deca8ae 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.
2026-02-25 10:15:47 -03:00

201 lines
No EOL
12 KiB
HTML

<style>
/* CRITICAL: Overriding default Tailwind resets that break the layout */
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
font-family: 'Fira Code', 'Fira Sans', Arial, sans-serif !important;
background: white !important;
overflow: hidden !important;
height: 100vh !important;
width: 100vw !important;
display: flex !important;
}
/* Core Layout replicating BUILD V3 screenshot styling */
.build-container { width: 100%; height: 100vh; display: flex; position: absolute; inset: 0; z-index: 1000; background: white;}
/* Left Sidebar */
.sidebar { width: 51px; height: 100vh; background: #f8f8f8; border-right: 1px solid #f0f1f2; display: flex; flex-direction: column; z-index: 100; }
.sidebar-item { width: 51px; height: 50px; border-bottom: 1px solid #f0f1f2; display: flex; align-items: center; justify-content: center; cursor: pointer; transition: all 0.15s ease; position: relative;}
.sidebar-item:hover { background: #ffffff; }
.sidebar-item.active { background: #ffffff; border-left: 3px solid #84d669; }
.sidebar-icon { width: 30px; height: 30px; opacity: 0.6; }
.sidebar-item:hover .sidebar-icon { opacity: 1; }
/* Main Content wrapper */
.main-wrapper { flex: 1; display: flex; flex-direction: column; overflow: hidden; position: relative; }
/* Top Navigation Tabs */
.tabs-container { display: flex; flex-direction: column; background: #f8f8f8; border-bottom: 1px solid #f0f1f2; z-index: 100;}
.tabs-row { display: flex; height: 34px; }
.main-tab { height: 34px; min-width: 169px; flex: 1; border-right: 1px solid #f0f1f2; display: flex; align-items: center; padding: 0 18px; cursor: pointer; position: relative; }
.main-tab:hover { background: #ffffff; }
.main-tab.active { background: #84d669; border-color: #84d669;}
.main-tab.active .main-tab-content { color: white; }
.main-tab-content { display: flex; align-items: center; gap: 4px; font-family: 'Fira Code', monospace; font-size: 14px; font-weight: 500; color: #3b3b3b; }
/* Workspace (Where windows float) */
.workspace { flex: 1; display: flex; flex-direction: column; overflow: hidden; background: white; position: relative; z-index: 10;}
/* The Panel Grid (Desktop Icons) */
.panel-section { flex: 1; padding: 26px 33px; overflow-y: auto; z-index: 10; position: absolute; inset: 0; pointer-events: none;}
.panel-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 20px; max-width: 1200px; pointer-events: auto;}
/* Interactive Desktop Icons triggering HTMX */
.desktop-icon {
background: white; border: 1px solid #f0f1f2; border-radius: 8px; height: 67px; padding: 10px;
display: flex; flex-direction: column; justify-content: flex-end; align-items: flex-start;
cursor: pointer; transition: all 0.2s ease; position: relative; width: 100%;
}
.desktop-icon:hover { box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1); transform: translateY(-2px); border-color: #84d669; }
.panel-card-icon { position: absolute; top: -20px; left: 10px; width: 42px; height: 42px; }
.panel-card-label { font-family: 'Fira Code', monospace; font-size: 13px; font-weight: 500; color: #3b3b3b; margin-top: auto; }
/* The background abstract pattern from BUILD V3 */
.bg-grid { position: absolute; inset: 0; background-image: linear-gradient(to right, #f0fdf4 1px, transparent 1px), linear-gradient(to bottom, #f0fdf4 1px, transparent 1px); background-size: 40px 40px; z-index: 0; pointer-events: none; }
.bg-svg { position: absolute; top: -10%; left: -10%; width: 120%; height: 120%; z-index: 0; opacity: 0.6; pointer-events: none; display: block !important;}
.bg-svg path { fill: none; stroke: #e6f2eb; stroke-width: 45; stroke-linecap: round; stroke-linejoin: round; }
.bg-svg path.inner { stroke: #f7faf9; stroke-width: 41; }
/* Bottom Taskbar */
.toolbar { height: 50px; background: white; border-top: 1px solid #f0f1f2; display: flex; align-items: center; padding: 0 8px; z-index: 100; position: relative;}
#taskbar-apps { display: flex; flex: 1; height: 100%; align-items: center; gap: 0px; }
.toolbar-time { font-family: 'Fira Code', monospace; font-size: 14px; color: #3b3b3b; text-align: right; line-height: 1.4; padding: 0 10px; margin-left: auto; }
/* Taskbar Items generated by WindowManager */
.taskbar-item { width: 40px; height: 40px; display: flex; align-items: center; justify-content: center; cursor: pointer; transition: all 0.15s ease; border-bottom: 2px solid transparent;}
.taskbar-item:hover { background: #f8f8f8; }
.taskbar-item.active { border-bottom-color: #84d669; background: linear-gradient(to bottom, rgba(132,214,105,0) 50%, rgba(132,214,105,0.1) 100%); }
/* Utility */
svg { display: block; }
</style>
<div class="build-container">
<!-- Left Sidebar -->
<aside class="sidebar">
<div class="sidebar-item active" title="Home">
<svg class="sidebar-icon" viewBox="0 0 24 24" fill="none" stroke="#3b3b3b" stroke-width="2"><path d="M3 9l9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"></path><polyline points="9 22 9 12 15 12 15 22"></polyline></svg>
</div>
<div class="sidebar-item" title="Terminal">
<svg class="sidebar-icon" viewBox="0 0 24 24" fill="none" stroke="#3b3b3b" stroke-width="2"><polyline points="4 17 10 11 4 5"></polyline><line x1="12" y1="19" x2="20" y2="19"></line></svg>
</div>
</aside>
<!-- Main Wrapper -->
<div class="main-wrapper">
<!-- Top Navigation Tabs -->
<div class="tabs-container">
<div class="tabs-row">
<div class="main-tab active"><div class="main-tab-content"><span>//</span><span>BUILD</span></div></div>
<div class="main-tab"><div class="main-tab-content"><span>//</span><span>REVIEW</span></div></div>
<div class="main-tab"><div class="main-tab-content"><span>//</span><span>DEPLOY</span></div></div>
<div class="main-tab"><div class="main-tab-content"><span>//</span><span>MONITOR</span></div></div>
</div>
</div>
<!-- Workspace container where WindowManager operates -->
<div class="workspace" id="desktop-content-inner">
<!-- Background Pattern -->
<div class="bg-grid"></div>
<svg class="bg-svg" preserveAspectRatio="xMidYMid slice" viewBox="0 0 1000 600">
<path d="M-50,200 Q200,100 400,250 T800,50 T1100,250" />
<path d="M100,-50 Q250,200 150,450 T400,650" />
<path d="M500,-50 Q450,250 800,350 T750,700" />
<path class="inner" d="M-50,200 Q200,100 400,250 T800,50 T1100,250" />
<path class="inner" d="M100,-50 Q250,200 150,450 T400,650" />
<path class="inner" d="M500,-50 Q450,250 800,350 T750,700" />
</svg>
<div class="panel-section">
<div class="panel-grid">
<!-- HTMX Enabled Desktop Icons that WindowManager catches -->
<div class="desktop-icon" data-app-id="vibe" data-app-title="Vibe" hx-get="/suite/partials/chat.html" hx-swap="none">
<svg class="panel-card-icon" viewBox="0 0 42 42" fill="none">
<circle cx="21" cy="21" r="20" stroke="#84d669" stroke-width="2"/>
<path d="M14 21h14M21 14v14" stroke="#84d669" stroke-width="2"/>
</svg>
<div class="panel-card-label">Mantis</div>
</div>
<div class="desktop-icon" data-app-id="tasks" data-app-title="Tasks" hx-get="/suite/partials/tasks.html" hx-swap="none">
<svg class="panel-card-icon" viewBox="0 0 42 42" fill="none">
<rect x="2" y="2" width="38" height="38" rx="4" stroke="#3b3b3b" stroke-width="2"/>
<line x1="12" y1="12" x2="30" y2="12" stroke="#3b3b3b" stroke-width="2"/>
<line x1="12" y1="21" x2="30" y2="21" stroke="#3b3b3b" stroke-width="2"/>
<line x1="12" y1="30" x2="24" y2="30" stroke="#3b3b3b" stroke-width="2"/>
</svg>
<div class="panel-card-label">Tasks</div>
</div>
<div class="desktop-icon" data-app-id="terminal" data-app-title="Terminal" hx-get="/suite/partials/terminal.html" hx-swap="none">
<svg class="panel-card-icon" viewBox="0 0 42 42" fill="none">
<rect x="2" y="4" width="38" height="34" rx="4" stroke="#3b3b3b" stroke-width="2"/>
<line x1="10" y1="14" x2="32" y2="14" stroke="#3b3b3b" stroke-width="2"/>
<line x1="10" y1="22" x2="28" y2="22" stroke="#3b3b3b" stroke-width="2"/>
<line x1="10" y1="30" x2="24" y2="30" stroke="#3b3b3b" stroke-width="2"/>
</svg>
<div class="panel-card-label">Terminal</div>
</div>
<div class="desktop-icon" data-app-id="explorer" data-app-title="Explorer" hx-get="/suite/partials/explorer.html" hx-swap="none">
<svg class="panel-card-icon" viewBox="0 0 42 42" fill="none">
<rect x="2" y="2" width="38" height="38" rx="4" stroke="#3b3b3b" stroke-width="2"/>
<line x1="10" y1="10" x2="32" y2="10" stroke="#3b3b3b" stroke-width="2"/>
<line x1="10" y1="18" x2="28" y2="18" stroke="#3b3b3b" stroke-width="2"/>
<line x1="10" y1="26" x2="32" y2="26" stroke="#3b3b3b" stroke-width="2"/>
<line x1="10" y1="34" x2="24" y2="34" stroke="#3b3b3b" stroke-width="2"/>
</svg>
<div class="panel-card-label">Explorer</div>
</div>
<div class="desktop-icon" data-app-id="editor" data-app-title="Editor" hx-get="/suite/partials/editor.html" hx-swap="none">
<svg class="panel-card-icon" viewBox="0 0 42 42" fill="none">
<polyline points="4 8 12 2 20 8" stroke="#3b3b3b" stroke-width="2"/>
<line x1="12" y1="2" x2="12" y2="24" stroke="#3b3b3b" stroke-width="2"/>
<polyline points="22 16 30 10 38 16" stroke="#3b3b3b" stroke-width="2"/>
<line x1="30" y1="10" x2="30" y2="32" stroke="#3b3b3b" stroke-width="2"/>
</svg>
<div class="panel-card-label">Editor</div>
</div>
<div class="desktop-icon" data-app-id="browser" data-app-title="Browser" hx-get="/suite/partials/browser.html" hx-swap="none">
<svg class="panel-card-icon" viewBox="0 0 42 42" fill="none">
<rect x="2" y="4" width="38" height="34" rx="4" stroke="#3b3b3b" stroke-width="2"/>
<circle cx="14" cy="16" r="4" stroke="#3b3b3b" stroke-width="2"/>
<path d="M6 34a6 6 0 0 1 6-6h10a6 6 0 0 1 6 6v2H6v-2z" stroke="#3b3b3b" stroke-width="2"/>
</svg>
<div class="panel-card-label">Browser</div>
</div>
</div>
</div>
</div>
<!-- Bottom Taskbar -->
<footer class="toolbar" id="taskbar">
<div id="taskbar-apps">
<!-- Taskbar items populated automatically by window-manager.js -->
</div>
<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-date">01/01/2026</div>
</div>
</div>
</footer>
</div>
</div>
<!-- HTMX Intercepts and WindowManager Init as described in UI.md Phase 3 -->
<script>
// Simple Clock implementation matching the screenshot bottom right corner
setInterval(() => {
const now = new Date();
const timeEl = document.getElementById('clock-time');
const dateEl = document.getElementById('clock-date');
if(timeEl) timeEl.textContent = now.toLocaleTimeString([], {hour: '2-digit', minute:'2-digit'});
if(dateEl) dateEl.textContent = now.toLocaleDateString();
}, 1000);
</script>