From c17dc57fd99fb9cb8266ea0a41437dc31e2c9db5 Mon Sep 17 00:00:00 2001 From: arkon Date: Wed, 28 Jan 2026 06:03:48 +0100 Subject: [PATCH] feat(wizard): add minimize button and network retry for Ralph wizard - Add minimize button to Ralph wizard modal header - Minimized indicator shows at bottom of screen with elapsed time - Click indicator to restore wizard - Add retry logic (3 attempts with exponential backoff) for plan generation - Shows "Connection lost, retrying..." message during retries Co-Authored-By: Claude Opus 4.5 --- CLAUDE.md | 2 +- package.json | 2 +- src/web/public/app.js | 87 +++++++++++++++++++++++++++++++++++---- src/web/public/index.html | 8 ++++ src/web/public/styles.css | 55 +++++++++++++++++++++++++ 5 files changed, 145 insertions(+), 9 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index 1027a618..11b8deb3 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -16,7 +16,7 @@ When user says "COM": 1. Increment version in BOTH `package.json` AND `CLAUDE.md` 2. Run: `git add -A && git commit -m "chore: bump version to X.XXXX" && git push && npm run build && systemctl --user restart claudeman-web` -**Version**: 0.1413 (must match `package.json`) +**Version**: 0.1414 (must match `package.json`) ## Project Overview diff --git a/package.json b/package.json index 0454e9ab..d44555a8 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "claudeman", - "version": "0.1413", + "version": "0.1414", "description": "The missing control plane for Claude Code - run 20 autonomous agents with real-time monitoring and session persistence", "type": "module", "main": "dist/index.js", diff --git a/src/web/public/app.js b/src/web/public/app.js index c60787cb..b2915377 100644 --- a/src/web/public/app.js +++ b/src/web/public/app.js @@ -2715,6 +2715,9 @@ class ClaudemanApp { // Remove agents minimized tab if exists this.removeAgentsMinimizedTab(); + // Hide minimized indicator + this.hideWizardMinimizedIndicator(); + // Reset wizard position this.wizardPosition = null; this.planAgentsMinimized = false; @@ -2723,6 +2726,49 @@ class ClaudemanApp { this.updateConnectionLines(); } + minimizeRalphWizard() { + const modal = document.getElementById('ralphWizardModal'); + modal?.classList.remove('active'); + + // Show the minimized indicator + this.showWizardMinimizedIndicator(); + } + + restoreRalphWizard() { + // Hide the minimized indicator + this.hideWizardMinimizedIndicator(); + + // Show the modal again + const modal = document.getElementById('ralphWizardModal'); + modal?.classList.add('active'); + } + + showWizardMinimizedIndicator() { + const indicator = document.getElementById('wizardMinimizedIndicator'); + if (!indicator) return; + + indicator.classList.add('visible'); + + // Start time counter if plan generation is active + if (this.planLoadingStartTime) { + this.wizardMinimizedTimer = setInterval(() => { + const elapsed = Math.floor((Date.now() - this.planLoadingStartTime) / 1000); + const timeEl = document.getElementById('wizardMinimizedTime'); + if (timeEl) timeEl.textContent = `${elapsed}s`; + }, 1000); + } + } + + hideWizardMinimizedIndicator() { + const indicator = document.getElementById('wizardMinimizedIndicator'); + if (indicator) indicator.classList.remove('visible'); + + if (this.wizardMinimizedTimer) { + clearInterval(this.wizardMinimizedTimer); + this.wizardMinimizedTimer = null; + } + } + populateRalphCaseSelector() { const select = document.getElementById('ralphCaseSelect'); const quickStartSelect = document.getElementById('quickStartCase'); @@ -3296,14 +3342,41 @@ class ClaudemanApp { ? { taskDescription: config.taskDescription, caseName: config.caseName } : { taskDescription: config.taskDescription, detailLevel: config.planDetailLevel }; - const res = await fetch(endpoint, { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify(body), - signal: this.planGenerationAbortController?.signal, - }); + // Retry logic for network errors + const maxRetries = 3; + let lastError = null; + let data = null; - const data = await res.json(); + for (let attempt = 0; attempt < maxRetries; attempt++) { + try { + const res = await fetch(endpoint, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(body), + signal: this.planGenerationAbortController?.signal, + }); + data = await res.json(); + break; // Success, exit retry loop + } catch (fetchErr) { + lastError = fetchErr; + // Don't retry if aborted + if (fetchErr.name === 'AbortError') throw fetchErr; + // Network error - retry with exponential backoff + console.warn(`[Plan] Fetch attempt ${attempt + 1} failed:`, fetchErr.message); + if (attempt < maxRetries - 1) { + const delay = Math.pow(2, attempt) * 1000; // 1s, 2s, 4s + const titleEl = document.getElementById('planLoadingTitle'); + const hintEl = document.getElementById('planLoadingHint'); + if (titleEl) titleEl.textContent = 'Connection lost, retrying...'; + if (hintEl) hintEl.textContent = `Attempt ${attempt + 2} of ${maxRetries} in ${delay / 1000}s`; + await new Promise(r => setTimeout(r, delay)); + } + } + } + + if (!data) { + throw lastError || new Error('Failed to fetch after retries'); + } // Stop timer if (this.planLoadingTimer) { diff --git a/src/web/public/index.html b/src/web/public/index.html index 11ba7faa..2a88fd1c 100644 --- a/src/web/public/index.html +++ b/src/web/public/index.html @@ -951,6 +951,7 @@ 🤖 0 + @@ -1143,6 +1144,13 @@ + +
+ 🔄 + Ralph Wizard + 0s +
+