From 0e557be7b8a51574dfad7052f599287f26195c1e Mon Sep 17 00:00:00 2001 From: ciregenz Date: Fri, 12 Jun 2026 18:21:31 -0700 Subject: [PATCH] [eric] views: gate app runtime/start behind previewSettled so spam-switching doesn't queue a pile of vite boots that stick the landed app on Initializing agent --- frontend/src/app/pages/Views/ViewEditor.tsx | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/frontend/src/app/pages/Views/ViewEditor.tsx b/frontend/src/app/pages/Views/ViewEditor.tsx index add0938b..f98bbc42 100644 --- a/frontend/src/app/pages/Views/ViewEditor.tsx +++ b/frontend/src/app/pages/Views/ViewEditor.tsx @@ -872,13 +872,18 @@ const ViewEditor: React.FC = ({ output }) => { }, [workspaceId, runtimeShouldRun, appendTerminalLine]); // One-shot trigger: first visit to Preview/Terminal flips runtimeShouldRun true; never flips back. + // Gated on previewSettled (same 250ms as the webview): each runtime/start boots a vite + // dev server serialized behind a global lock, so spam-clicking heavy apps used to queue a + // pile of vite boots that saturated the backend and stuck the app you landed on (preview + // frozen + "Initializing agent..."). Waiting out the debounce means only apps you actually + // stay on boot a runtime. useEffect(() => { - if (!workspaceId) return; + if (!workspaceId || !previewSettled) return; if (runtimeShouldRun) return; const wantsRuntime = activeTab === TAB_PREVIEW || activeTab === TAB_TERMINAL; if (!wantsRuntime) return; setRuntimeShouldRun(true); - }, [workspaceId, activeTab, runtimeShouldRun, TAB_PREVIEW, TAB_TERMINAL]); + }, [workspaceId, activeTab, runtimeShouldRun, previewSettled, TAB_PREVIEW, TAB_TERMINAL]); // Prefer the Vite dev server URL; fall back to legacy /serve/. New-mode pre-Vite renders the install placeholder (legacy URL 404s). const showInstallPlaceholder = isNewModeRuntime && !frontendUrl;