From 7011a523e0d2018b896c478c994fda79d60a5ad1 Mon Sep 17 00:00:00 2001 From: Paul Bakaus Date: Sun, 12 Apr 2026 19:25:29 -0700 Subject: [PATCH] Remove live commands from CLI, delete src/live, add server-lost cleanup 1. CLI cleanup: removed live, poll, and wrap commands from bin/cli.js and the liveCli export from detect-antipatterns.mjs. These now live exclusively in the skill scripts (node scripts_path/live-server.mjs). 2. Deleted src/live/: server.mjs, poll.mjs, wrap.mjs, browser.js, protocol.mjs. The source of truth is now source/skills/impeccable/ scripts/live-*. 3. Graceful server-lost handling: the browser tracks SSE reconnection attempts (max 5). After exhausting retries, it cleans up the UI: hides the bar, highlight, and cycler, shows a "Live server disconnected" toast, resets state to IDLE. This handles agent crashes, server kills, and network issues without leaving the browser stuck in a "Generating..." state. Co-Authored-By: Claude Opus 4.6 (1M context) --- .../skills/impeccable/scripts/live-browser.js | 31 +- .../skills/impeccable/scripts/live-browser.js | 31 +- .../skills/impeccable/scripts/live-browser.js | 31 +- .../skills/impeccable/scripts/live-browser.js | 31 +- .../skills/impeccable/scripts/live-browser.js | 31 +- .../skills/impeccable/scripts/live-browser.js | 31 +- .../skills/impeccable/scripts/live-browser.js | 31 +- .pi/skills/impeccable/scripts/live-browser.js | 31 +- .../skills/impeccable/scripts/live-browser.js | 31 +- .../skills/impeccable/scripts/live-browser.js | 31 +- .../skills/impeccable/scripts/live-browser.js | 31 +- bin/cli.js | 19 - .../skills/impeccable/scripts/live-browser.js | 31 +- src/detect-antipatterns.mjs | 10 - src/live/browser.js | 1220 ----------------- src/live/poll.mjs | 123 -- src/live/protocol.mjs | 79 -- src/live/server.mjs | 496 ------- src/live/wrap.mjs | 298 ---- 19 files changed, 348 insertions(+), 2269 deletions(-) delete mode 100644 src/live/browser.js delete mode 100644 src/live/poll.mjs delete mode 100644 src/live/protocol.mjs delete mode 100644 src/live/server.mjs delete mode 100644 src/live/wrap.mjs diff --git a/.agents/skills/impeccable/scripts/live-browser.js b/.agents/skills/impeccable/scripts/live-browser.js index 7a26c9829..72534661b 100644 --- a/.agents/skills/impeccable/scripts/live-browser.js +++ b/.agents/skills/impeccable/scripts/live-browser.js @@ -870,11 +870,14 @@ // --------------------------------------------------------------------------- let evtSource = null; + let sseRetries = 0; + const SSE_MAX_RETRIES = 5; function connectSSE() { evtSource = new EventSource('http://localhost:' + PORT + '/events?token=' + TOKEN); evtSource.onmessage = (e) => { + sseRetries = 0; // reset on any successful message let msg; try { msg = JSON.parse(e.data); } catch { return; } switch (msg.type) { case 'connected': @@ -918,11 +921,35 @@ }; evtSource.onerror = () => { - // EventSource auto-reconnects. Log once. - console.log('[impeccable] SSE connection lost. Reconnecting...'); + sseRetries++; + if (sseRetries <= SSE_MAX_RETRIES) { + console.log('[impeccable] SSE connection lost. Retry ' + sseRetries + '/' + SSE_MAX_RETRIES + '...'); + return; // EventSource auto-reconnects + } + // Server is gone. Clean up gracefully. + console.log('[impeccable] Live server unreachable. Cleaning up UI.'); + evtSource.close(); + evtSource = null; + handleServerLost(); }; } + /** Server died or became unreachable. Reset UI to a clean state. */ + function handleServerLost() { + if (state === 'GENERATING' || state === 'CYCLING' || state === 'SAVING') { + showToast('Live server disconnected. Session ended.', 5000); + } + hideBar(); + hideHighlight(); + stopScrollTracking(); + if (variantObserver) { variantObserver.disconnect(); variantObserver = null; } + clearSession(); + selectedElement = null; + currentSessionId = null; + selectedAction = 'impeccable'; + state = 'IDLE'; + } + function sendEvent(msg) { msg.token = TOKEN; fetch('http://localhost:' + PORT + '/events', { diff --git a/.claude/skills/impeccable/scripts/live-browser.js b/.claude/skills/impeccable/scripts/live-browser.js index 7a26c9829..72534661b 100644 --- a/.claude/skills/impeccable/scripts/live-browser.js +++ b/.claude/skills/impeccable/scripts/live-browser.js @@ -870,11 +870,14 @@ // --------------------------------------------------------------------------- let evtSource = null; + let sseRetries = 0; + const SSE_MAX_RETRIES = 5; function connectSSE() { evtSource = new EventSource('http://localhost:' + PORT + '/events?token=' + TOKEN); evtSource.onmessage = (e) => { + sseRetries = 0; // reset on any successful message let msg; try { msg = JSON.parse(e.data); } catch { return; } switch (msg.type) { case 'connected': @@ -918,11 +921,35 @@ }; evtSource.onerror = () => { - // EventSource auto-reconnects. Log once. - console.log('[impeccable] SSE connection lost. Reconnecting...'); + sseRetries++; + if (sseRetries <= SSE_MAX_RETRIES) { + console.log('[impeccable] SSE connection lost. Retry ' + sseRetries + '/' + SSE_MAX_RETRIES + '...'); + return; // EventSource auto-reconnects + } + // Server is gone. Clean up gracefully. + console.log('[impeccable] Live server unreachable. Cleaning up UI.'); + evtSource.close(); + evtSource = null; + handleServerLost(); }; } + /** Server died or became unreachable. Reset UI to a clean state. */ + function handleServerLost() { + if (state === 'GENERATING' || state === 'CYCLING' || state === 'SAVING') { + showToast('Live server disconnected. Session ended.', 5000); + } + hideBar(); + hideHighlight(); + stopScrollTracking(); + if (variantObserver) { variantObserver.disconnect(); variantObserver = null; } + clearSession(); + selectedElement = null; + currentSessionId = null; + selectedAction = 'impeccable'; + state = 'IDLE'; + } + function sendEvent(msg) { msg.token = TOKEN; fetch('http://localhost:' + PORT + '/events', { diff --git a/.codex/skills/impeccable/scripts/live-browser.js b/.codex/skills/impeccable/scripts/live-browser.js index 7a26c9829..72534661b 100644 --- a/.codex/skills/impeccable/scripts/live-browser.js +++ b/.codex/skills/impeccable/scripts/live-browser.js @@ -870,11 +870,14 @@ // --------------------------------------------------------------------------- let evtSource = null; + let sseRetries = 0; + const SSE_MAX_RETRIES = 5; function connectSSE() { evtSource = new EventSource('http://localhost:' + PORT + '/events?token=' + TOKEN); evtSource.onmessage = (e) => { + sseRetries = 0; // reset on any successful message let msg; try { msg = JSON.parse(e.data); } catch { return; } switch (msg.type) { case 'connected': @@ -918,11 +921,35 @@ }; evtSource.onerror = () => { - // EventSource auto-reconnects. Log once. - console.log('[impeccable] SSE connection lost. Reconnecting...'); + sseRetries++; + if (sseRetries <= SSE_MAX_RETRIES) { + console.log('[impeccable] SSE connection lost. Retry ' + sseRetries + '/' + SSE_MAX_RETRIES + '...'); + return; // EventSource auto-reconnects + } + // Server is gone. Clean up gracefully. + console.log('[impeccable] Live server unreachable. Cleaning up UI.'); + evtSource.close(); + evtSource = null; + handleServerLost(); }; } + /** Server died or became unreachable. Reset UI to a clean state. */ + function handleServerLost() { + if (state === 'GENERATING' || state === 'CYCLING' || state === 'SAVING') { + showToast('Live server disconnected. Session ended.', 5000); + } + hideBar(); + hideHighlight(); + stopScrollTracking(); + if (variantObserver) { variantObserver.disconnect(); variantObserver = null; } + clearSession(); + selectedElement = null; + currentSessionId = null; + selectedAction = 'impeccable'; + state = 'IDLE'; + } + function sendEvent(msg) { msg.token = TOKEN; fetch('http://localhost:' + PORT + '/events', { diff --git a/.cursor/skills/impeccable/scripts/live-browser.js b/.cursor/skills/impeccable/scripts/live-browser.js index 7a26c9829..72534661b 100644 --- a/.cursor/skills/impeccable/scripts/live-browser.js +++ b/.cursor/skills/impeccable/scripts/live-browser.js @@ -870,11 +870,14 @@ // --------------------------------------------------------------------------- let evtSource = null; + let sseRetries = 0; + const SSE_MAX_RETRIES = 5; function connectSSE() { evtSource = new EventSource('http://localhost:' + PORT + '/events?token=' + TOKEN); evtSource.onmessage = (e) => { + sseRetries = 0; // reset on any successful message let msg; try { msg = JSON.parse(e.data); } catch { return; } switch (msg.type) { case 'connected': @@ -918,11 +921,35 @@ }; evtSource.onerror = () => { - // EventSource auto-reconnects. Log once. - console.log('[impeccable] SSE connection lost. Reconnecting...'); + sseRetries++; + if (sseRetries <= SSE_MAX_RETRIES) { + console.log('[impeccable] SSE connection lost. Retry ' + sseRetries + '/' + SSE_MAX_RETRIES + '...'); + return; // EventSource auto-reconnects + } + // Server is gone. Clean up gracefully. + console.log('[impeccable] Live server unreachable. Cleaning up UI.'); + evtSource.close(); + evtSource = null; + handleServerLost(); }; } + /** Server died or became unreachable. Reset UI to a clean state. */ + function handleServerLost() { + if (state === 'GENERATING' || state === 'CYCLING' || state === 'SAVING') { + showToast('Live server disconnected. Session ended.', 5000); + } + hideBar(); + hideHighlight(); + stopScrollTracking(); + if (variantObserver) { variantObserver.disconnect(); variantObserver = null; } + clearSession(); + selectedElement = null; + currentSessionId = null; + selectedAction = 'impeccable'; + state = 'IDLE'; + } + function sendEvent(msg) { msg.token = TOKEN; fetch('http://localhost:' + PORT + '/events', { diff --git a/.gemini/skills/impeccable/scripts/live-browser.js b/.gemini/skills/impeccable/scripts/live-browser.js index 7a26c9829..72534661b 100644 --- a/.gemini/skills/impeccable/scripts/live-browser.js +++ b/.gemini/skills/impeccable/scripts/live-browser.js @@ -870,11 +870,14 @@ // --------------------------------------------------------------------------- let evtSource = null; + let sseRetries = 0; + const SSE_MAX_RETRIES = 5; function connectSSE() { evtSource = new EventSource('http://localhost:' + PORT + '/events?token=' + TOKEN); evtSource.onmessage = (e) => { + sseRetries = 0; // reset on any successful message let msg; try { msg = JSON.parse(e.data); } catch { return; } switch (msg.type) { case 'connected': @@ -918,11 +921,35 @@ }; evtSource.onerror = () => { - // EventSource auto-reconnects. Log once. - console.log('[impeccable] SSE connection lost. Reconnecting...'); + sseRetries++; + if (sseRetries <= SSE_MAX_RETRIES) { + console.log('[impeccable] SSE connection lost. Retry ' + sseRetries + '/' + SSE_MAX_RETRIES + '...'); + return; // EventSource auto-reconnects + } + // Server is gone. Clean up gracefully. + console.log('[impeccable] Live server unreachable. Cleaning up UI.'); + evtSource.close(); + evtSource = null; + handleServerLost(); }; } + /** Server died or became unreachable. Reset UI to a clean state. */ + function handleServerLost() { + if (state === 'GENERATING' || state === 'CYCLING' || state === 'SAVING') { + showToast('Live server disconnected. Session ended.', 5000); + } + hideBar(); + hideHighlight(); + stopScrollTracking(); + if (variantObserver) { variantObserver.disconnect(); variantObserver = null; } + clearSession(); + selectedElement = null; + currentSessionId = null; + selectedAction = 'impeccable'; + state = 'IDLE'; + } + function sendEvent(msg) { msg.token = TOKEN; fetch('http://localhost:' + PORT + '/events', { diff --git a/.kiro/skills/impeccable/scripts/live-browser.js b/.kiro/skills/impeccable/scripts/live-browser.js index 7a26c9829..72534661b 100644 --- a/.kiro/skills/impeccable/scripts/live-browser.js +++ b/.kiro/skills/impeccable/scripts/live-browser.js @@ -870,11 +870,14 @@ // --------------------------------------------------------------------------- let evtSource = null; + let sseRetries = 0; + const SSE_MAX_RETRIES = 5; function connectSSE() { evtSource = new EventSource('http://localhost:' + PORT + '/events?token=' + TOKEN); evtSource.onmessage = (e) => { + sseRetries = 0; // reset on any successful message let msg; try { msg = JSON.parse(e.data); } catch { return; } switch (msg.type) { case 'connected': @@ -918,11 +921,35 @@ }; evtSource.onerror = () => { - // EventSource auto-reconnects. Log once. - console.log('[impeccable] SSE connection lost. Reconnecting...'); + sseRetries++; + if (sseRetries <= SSE_MAX_RETRIES) { + console.log('[impeccable] SSE connection lost. Retry ' + sseRetries + '/' + SSE_MAX_RETRIES + '...'); + return; // EventSource auto-reconnects + } + // Server is gone. Clean up gracefully. + console.log('[impeccable] Live server unreachable. Cleaning up UI.'); + evtSource.close(); + evtSource = null; + handleServerLost(); }; } + /** Server died or became unreachable. Reset UI to a clean state. */ + function handleServerLost() { + if (state === 'GENERATING' || state === 'CYCLING' || state === 'SAVING') { + showToast('Live server disconnected. Session ended.', 5000); + } + hideBar(); + hideHighlight(); + stopScrollTracking(); + if (variantObserver) { variantObserver.disconnect(); variantObserver = null; } + clearSession(); + selectedElement = null; + currentSessionId = null; + selectedAction = 'impeccable'; + state = 'IDLE'; + } + function sendEvent(msg) { msg.token = TOKEN; fetch('http://localhost:' + PORT + '/events', { diff --git a/.opencode/skills/impeccable/scripts/live-browser.js b/.opencode/skills/impeccable/scripts/live-browser.js index 7a26c9829..72534661b 100644 --- a/.opencode/skills/impeccable/scripts/live-browser.js +++ b/.opencode/skills/impeccable/scripts/live-browser.js @@ -870,11 +870,14 @@ // --------------------------------------------------------------------------- let evtSource = null; + let sseRetries = 0; + const SSE_MAX_RETRIES = 5; function connectSSE() { evtSource = new EventSource('http://localhost:' + PORT + '/events?token=' + TOKEN); evtSource.onmessage = (e) => { + sseRetries = 0; // reset on any successful message let msg; try { msg = JSON.parse(e.data); } catch { return; } switch (msg.type) { case 'connected': @@ -918,11 +921,35 @@ }; evtSource.onerror = () => { - // EventSource auto-reconnects. Log once. - console.log('[impeccable] SSE connection lost. Reconnecting...'); + sseRetries++; + if (sseRetries <= SSE_MAX_RETRIES) { + console.log('[impeccable] SSE connection lost. Retry ' + sseRetries + '/' + SSE_MAX_RETRIES + '...'); + return; // EventSource auto-reconnects + } + // Server is gone. Clean up gracefully. + console.log('[impeccable] Live server unreachable. Cleaning up UI.'); + evtSource.close(); + evtSource = null; + handleServerLost(); }; } + /** Server died or became unreachable. Reset UI to a clean state. */ + function handleServerLost() { + if (state === 'GENERATING' || state === 'CYCLING' || state === 'SAVING') { + showToast('Live server disconnected. Session ended.', 5000); + } + hideBar(); + hideHighlight(); + stopScrollTracking(); + if (variantObserver) { variantObserver.disconnect(); variantObserver = null; } + clearSession(); + selectedElement = null; + currentSessionId = null; + selectedAction = 'impeccable'; + state = 'IDLE'; + } + function sendEvent(msg) { msg.token = TOKEN; fetch('http://localhost:' + PORT + '/events', { diff --git a/.pi/skills/impeccable/scripts/live-browser.js b/.pi/skills/impeccable/scripts/live-browser.js index 7a26c9829..72534661b 100644 --- a/.pi/skills/impeccable/scripts/live-browser.js +++ b/.pi/skills/impeccable/scripts/live-browser.js @@ -870,11 +870,14 @@ // --------------------------------------------------------------------------- let evtSource = null; + let sseRetries = 0; + const SSE_MAX_RETRIES = 5; function connectSSE() { evtSource = new EventSource('http://localhost:' + PORT + '/events?token=' + TOKEN); evtSource.onmessage = (e) => { + sseRetries = 0; // reset on any successful message let msg; try { msg = JSON.parse(e.data); } catch { return; } switch (msg.type) { case 'connected': @@ -918,11 +921,35 @@ }; evtSource.onerror = () => { - // EventSource auto-reconnects. Log once. - console.log('[impeccable] SSE connection lost. Reconnecting...'); + sseRetries++; + if (sseRetries <= SSE_MAX_RETRIES) { + console.log('[impeccable] SSE connection lost. Retry ' + sseRetries + '/' + SSE_MAX_RETRIES + '...'); + return; // EventSource auto-reconnects + } + // Server is gone. Clean up gracefully. + console.log('[impeccable] Live server unreachable. Cleaning up UI.'); + evtSource.close(); + evtSource = null; + handleServerLost(); }; } + /** Server died or became unreachable. Reset UI to a clean state. */ + function handleServerLost() { + if (state === 'GENERATING' || state === 'CYCLING' || state === 'SAVING') { + showToast('Live server disconnected. Session ended.', 5000); + } + hideBar(); + hideHighlight(); + stopScrollTracking(); + if (variantObserver) { variantObserver.disconnect(); variantObserver = null; } + clearSession(); + selectedElement = null; + currentSessionId = null; + selectedAction = 'impeccable'; + state = 'IDLE'; + } + function sendEvent(msg) { msg.token = TOKEN; fetch('http://localhost:' + PORT + '/events', { diff --git a/.rovodev/skills/impeccable/scripts/live-browser.js b/.rovodev/skills/impeccable/scripts/live-browser.js index 7a26c9829..72534661b 100644 --- a/.rovodev/skills/impeccable/scripts/live-browser.js +++ b/.rovodev/skills/impeccable/scripts/live-browser.js @@ -870,11 +870,14 @@ // --------------------------------------------------------------------------- let evtSource = null; + let sseRetries = 0; + const SSE_MAX_RETRIES = 5; function connectSSE() { evtSource = new EventSource('http://localhost:' + PORT + '/events?token=' + TOKEN); evtSource.onmessage = (e) => { + sseRetries = 0; // reset on any successful message let msg; try { msg = JSON.parse(e.data); } catch { return; } switch (msg.type) { case 'connected': @@ -918,11 +921,35 @@ }; evtSource.onerror = () => { - // EventSource auto-reconnects. Log once. - console.log('[impeccable] SSE connection lost. Reconnecting...'); + sseRetries++; + if (sseRetries <= SSE_MAX_RETRIES) { + console.log('[impeccable] SSE connection lost. Retry ' + sseRetries + '/' + SSE_MAX_RETRIES + '...'); + return; // EventSource auto-reconnects + } + // Server is gone. Clean up gracefully. + console.log('[impeccable] Live server unreachable. Cleaning up UI.'); + evtSource.close(); + evtSource = null; + handleServerLost(); }; } + /** Server died or became unreachable. Reset UI to a clean state. */ + function handleServerLost() { + if (state === 'GENERATING' || state === 'CYCLING' || state === 'SAVING') { + showToast('Live server disconnected. Session ended.', 5000); + } + hideBar(); + hideHighlight(); + stopScrollTracking(); + if (variantObserver) { variantObserver.disconnect(); variantObserver = null; } + clearSession(); + selectedElement = null; + currentSessionId = null; + selectedAction = 'impeccable'; + state = 'IDLE'; + } + function sendEvent(msg) { msg.token = TOKEN; fetch('http://localhost:' + PORT + '/events', { diff --git a/.trae-cn/skills/impeccable/scripts/live-browser.js b/.trae-cn/skills/impeccable/scripts/live-browser.js index 7a26c9829..72534661b 100644 --- a/.trae-cn/skills/impeccable/scripts/live-browser.js +++ b/.trae-cn/skills/impeccable/scripts/live-browser.js @@ -870,11 +870,14 @@ // --------------------------------------------------------------------------- let evtSource = null; + let sseRetries = 0; + const SSE_MAX_RETRIES = 5; function connectSSE() { evtSource = new EventSource('http://localhost:' + PORT + '/events?token=' + TOKEN); evtSource.onmessage = (e) => { + sseRetries = 0; // reset on any successful message let msg; try { msg = JSON.parse(e.data); } catch { return; } switch (msg.type) { case 'connected': @@ -918,11 +921,35 @@ }; evtSource.onerror = () => { - // EventSource auto-reconnects. Log once. - console.log('[impeccable] SSE connection lost. Reconnecting...'); + sseRetries++; + if (sseRetries <= SSE_MAX_RETRIES) { + console.log('[impeccable] SSE connection lost. Retry ' + sseRetries + '/' + SSE_MAX_RETRIES + '...'); + return; // EventSource auto-reconnects + } + // Server is gone. Clean up gracefully. + console.log('[impeccable] Live server unreachable. Cleaning up UI.'); + evtSource.close(); + evtSource = null; + handleServerLost(); }; } + /** Server died or became unreachable. Reset UI to a clean state. */ + function handleServerLost() { + if (state === 'GENERATING' || state === 'CYCLING' || state === 'SAVING') { + showToast('Live server disconnected. Session ended.', 5000); + } + hideBar(); + hideHighlight(); + stopScrollTracking(); + if (variantObserver) { variantObserver.disconnect(); variantObserver = null; } + clearSession(); + selectedElement = null; + currentSessionId = null; + selectedAction = 'impeccable'; + state = 'IDLE'; + } + function sendEvent(msg) { msg.token = TOKEN; fetch('http://localhost:' + PORT + '/events', { diff --git a/.trae/skills/impeccable/scripts/live-browser.js b/.trae/skills/impeccable/scripts/live-browser.js index 7a26c9829..72534661b 100644 --- a/.trae/skills/impeccable/scripts/live-browser.js +++ b/.trae/skills/impeccable/scripts/live-browser.js @@ -870,11 +870,14 @@ // --------------------------------------------------------------------------- let evtSource = null; + let sseRetries = 0; + const SSE_MAX_RETRIES = 5; function connectSSE() { evtSource = new EventSource('http://localhost:' + PORT + '/events?token=' + TOKEN); evtSource.onmessage = (e) => { + sseRetries = 0; // reset on any successful message let msg; try { msg = JSON.parse(e.data); } catch { return; } switch (msg.type) { case 'connected': @@ -918,11 +921,35 @@ }; evtSource.onerror = () => { - // EventSource auto-reconnects. Log once. - console.log('[impeccable] SSE connection lost. Reconnecting...'); + sseRetries++; + if (sseRetries <= SSE_MAX_RETRIES) { + console.log('[impeccable] SSE connection lost. Retry ' + sseRetries + '/' + SSE_MAX_RETRIES + '...'); + return; // EventSource auto-reconnects + } + // Server is gone. Clean up gracefully. + console.log('[impeccable] Live server unreachable. Cleaning up UI.'); + evtSource.close(); + evtSource = null; + handleServerLost(); }; } + /** Server died or became unreachable. Reset UI to a clean state. */ + function handleServerLost() { + if (state === 'GENERATING' || state === 'CYCLING' || state === 'SAVING') { + showToast('Live server disconnected. Session ended.', 5000); + } + hideBar(); + hideHighlight(); + stopScrollTracking(); + if (variantObserver) { variantObserver.disconnect(); variantObserver = null; } + clearSession(); + selectedElement = null; + currentSessionId = null; + selectedAction = 'impeccable'; + state = 'IDLE'; + } + function sendEvent(msg) { msg.token = TOKEN; fetch('http://localhost:' + PORT + '/events', { diff --git a/bin/cli.js b/bin/cli.js index b3aa1f37c..881f964f8 100755 --- a/bin/cli.js +++ b/bin/cli.js @@ -5,8 +5,6 @@ * * Usage: * npx impeccable detect [file-or-dir-or-url...] - * npx impeccable live [--port=PORT] - * npx impeccable live stop * npx impeccable skills help|install|update * npx impeccable --help */ @@ -24,11 +22,6 @@ if (!command || command === '--help' || command === '-h') { Commands: detect [file-or-dir-or-url...] Scan for UI anti-patterns and design quality issues - live [--port=PORT] Start live variant server (element picker + variant cycling) - live stop Stop a running live server - poll Wait for a browser event from the live server - poll --reply Reply to a pending event (done, error) - wrap --id ID --count N --query Q Find element in source and create variant wrapper skills help List all available skills and commands skills install Install impeccable skills into your project skills update Update skills to the latest version @@ -52,18 +45,6 @@ if (command === 'detect') { process.argv = [process.argv[0], process.argv[1], ...args.slice(1)]; const { detectCli } = await import('../src/detect-antipatterns.mjs'); await detectCli(); -} else if (command === 'live') { - // Delegate to the self-contained skill script (also works via node scripts_path/live-server.mjs) - process.argv = [process.argv[0], process.argv[1], ...args.slice(1)]; - await import('../source/skills/impeccable/scripts/live-server.mjs'); -} else if (command === 'poll') { - process.argv = [process.argv[0], process.argv[1], ...args.slice(1)]; - const { pollCli } = await import('../source/skills/impeccable/scripts/live-poll.mjs'); - await pollCli(); -} else if (command === 'wrap') { - process.argv = [process.argv[0], process.argv[1], ...args.slice(1)]; - const { wrapCli } = await import('../source/skills/impeccable/scripts/live-wrap.mjs'); - await wrapCli(); } else if (command === 'skills') { const { run } = await import('./commands/skills.mjs'); await run(args.slice(1)); diff --git a/source/skills/impeccable/scripts/live-browser.js b/source/skills/impeccable/scripts/live-browser.js index 7a26c9829..72534661b 100644 --- a/source/skills/impeccable/scripts/live-browser.js +++ b/source/skills/impeccable/scripts/live-browser.js @@ -870,11 +870,14 @@ // --------------------------------------------------------------------------- let evtSource = null; + let sseRetries = 0; + const SSE_MAX_RETRIES = 5; function connectSSE() { evtSource = new EventSource('http://localhost:' + PORT + '/events?token=' + TOKEN); evtSource.onmessage = (e) => { + sseRetries = 0; // reset on any successful message let msg; try { msg = JSON.parse(e.data); } catch { return; } switch (msg.type) { case 'connected': @@ -918,11 +921,35 @@ }; evtSource.onerror = () => { - // EventSource auto-reconnects. Log once. - console.log('[impeccable] SSE connection lost. Reconnecting...'); + sseRetries++; + if (sseRetries <= SSE_MAX_RETRIES) { + console.log('[impeccable] SSE connection lost. Retry ' + sseRetries + '/' + SSE_MAX_RETRIES + '...'); + return; // EventSource auto-reconnects + } + // Server is gone. Clean up gracefully. + console.log('[impeccable] Live server unreachable. Cleaning up UI.'); + evtSource.close(); + evtSource = null; + handleServerLost(); }; } + /** Server died or became unreachable. Reset UI to a clean state. */ + function handleServerLost() { + if (state === 'GENERATING' || state === 'CYCLING' || state === 'SAVING') { + showToast('Live server disconnected. Session ended.', 5000); + } + hideBar(); + hideHighlight(); + stopScrollTracking(); + if (variantObserver) { variantObserver.disconnect(); variantObserver = null; } + clearSession(); + selectedElement = null; + currentSessionId = null; + selectedAction = 'impeccable'; + state = 'IDLE'; + } + function sendEvent(msg) { msg.token = TOKEN; fetch('http://localhost:' + PORT + '/events', { diff --git a/src/detect-antipatterns.mjs b/src/detect-antipatterns.mjs index b0d861ac3..8c4a22a15 100644 --- a/src/detect-antipatterns.mjs +++ b/src/detect-antipatterns.mjs @@ -3442,15 +3442,6 @@ async function main() { process.exit(0); } -// --------------------------------------------------------------------------- -// Live detection server -// --------------------------------------------------------------------------- - -async function liveCli() { - const { startLiveServer } = await import('./live/server.mjs'); - await startLiveServer(); -} - // --------------------------------------------------------------------------- // Entry point // --------------------------------------------------------------------------- @@ -3475,7 +3466,6 @@ export { buildImportGraph, resolveImport, detectFrameworkConfig, isPortListening, FRAMEWORK_CONFIGS, main as detectCli, - liveCli, }; // @browser-strip-end diff --git a/src/live/browser.js b/src/live/browser.js deleted file mode 100644 index 7a26c9829..000000000 --- a/src/live/browser.js +++ /dev/null @@ -1,1220 +0,0 @@ -/** - * Impeccable Live Variant Mode — Browser Script - * - * Injected into the user's page via