diff --git a/packages/app/src/app/app.tsx b/packages/app/src/app/app.tsx index d024db2d8..bc63f2a90 100644 --- a/packages/app/src/app/app.tsx +++ b/packages/app/src/app/app.tsx @@ -1653,6 +1653,9 @@ export default function App() { }); createEffect(() => { + // Only auto-select a session when the user is on the session route. + // Switching workspaces while on the dashboard should not force navigation. + if (currentView() !== "session") return; if (!client()) return; if (!sessionsLoaded()) return; if (creatingSession()) return; @@ -4005,13 +4008,28 @@ export default function App() { return activeWorkspaceDisplay(); }); + // Avoid flashing the full-screen switch overlay for fast workspace switches. + // Only show it if a switch is still in progress after a short delay. + const [workspaceSwitchDelayElapsed, setWorkspaceSwitchDelayElapsed] = createSignal(false); + createEffect(() => { + if (typeof window === "undefined") return; + const switchingId = workspaceStore.connectingWorkspaceId(); + if (!switchingId) { + setWorkspaceSwitchDelayElapsed(false); + return; + } + + setWorkspaceSwitchDelayElapsed(false); + const timer = window.setTimeout(() => setWorkspaceSwitchDelayElapsed(true), 250); + onCleanup(() => window.clearTimeout(timer)); + }); + const workspaceSwitchOpen = createMemo(() => { if (booting()) return true; - if (workspaceStore.connectingWorkspaceId()) return true; + if (workspaceStore.connectingWorkspaceId()) return workspaceSwitchDelayElapsed(); if (!busy() || !busyLabel()) return false; const label = busyLabel(); return ( - label === "status.connecting" || label === "status.starting_engine" || label === "status.restarting_engine" ); diff --git a/packages/app/src/app/context/workspace.ts b/packages/app/src/app/context/workspace.ts index fb549f18c..900a092b0 100644 --- a/packages/app/src/app/context/workspace.ts +++ b/packages/app/src/app/context/workspace.ts @@ -473,7 +473,7 @@ export function createWorkspaceStore(options: { info.projectDir ?? undefined, { reason: "engine-refresh" }, auth ?? undefined, - { quiet: true }, + { quiet: true, navigate: false }, ) .catch(() => undefined) .finally(() => { @@ -592,6 +592,7 @@ export function createWorkspaceStore(options: { reason: "workspace-switch-openwork", }, resolvedAuth, + { navigate: false }, ); if (!ok) { @@ -648,12 +649,18 @@ export function createWorkspaceStore(options: { return false; } - const ok = await connectToServer(baseUrl, next.directory?.trim() || undefined, { - workspaceId: next.id, - workspaceType: next.workspaceType, - targetRoot: next.directory?.trim() ?? "", - reason: "workspace-switch-direct", - }); + const ok = await connectToServer( + baseUrl, + next.directory?.trim() || undefined, + { + workspaceId: next.id, + workspaceType: next.workspaceType, + targetRoot: next.directory?.trim() ?? "", + reason: "workspace-switch-direct", + }, + undefined, + { navigate: false }, + ); if (!ok) { updateWorkspaceConnectionState(id, { @@ -742,13 +749,54 @@ export function createWorkspaceStore(options: { options.setPendingPermissions([]); options.setSessionStatusById({}); - const ok = await startHost({ workspacePath: next.path }); - if (!ok) { - updateWorkspaceConnectionState(id, { - status: "error", - message: "Failed to start local engine.", - }); - return false; + // If a local host engine is already running (common when bouncing between remote/local), + // reuse it instead of restarting to keep switching snappy. + let connectedToLocalHost = false; + const existingEngine = engine(); + const runtime = existingEngine?.runtime ?? resolveEngineRuntime(); + const canReuseHost = + isTauriRuntime() && + Boolean(existingEngine?.running && existingEngine.baseUrl); + + if (canReuseHost && runtime === "openwrk") { + try { + await openwrkWorkspaceActivate({ + workspacePath: next.path, + name: next.displayName?.trim() || next.name?.trim() || null, + }); + await activateOpenworkHostWorkspace(next.path); + + const nextInfo = await engineInfo(); + setEngine(nextInfo); + + const username = nextInfo.opencodeUsername?.trim() ?? ""; + const password = nextInfo.opencodePassword?.trim() ?? ""; + const auth = username && password ? { username, password } : undefined; + setEngineAuth(auth ?? null); + + if (nextInfo.baseUrl) { + connectedToLocalHost = await connectToServer( + nextInfo.baseUrl, + nextInfo.projectDir ?? undefined, + { reason: "workspace-attach-local" }, + auth, + { navigate: false }, + ); + } + } catch { + connectedToLocalHost = false; + } + } + + if (!connectedToLocalHost) { + const ok = await startHost({ workspacePath: next.path, navigate: false }); + if (!ok) { + updateWorkspaceConnectionState(id, { + status: "error", + message: "Failed to start local engine.", + }); + return false; + } } } @@ -776,17 +824,18 @@ export function createWorkspaceStore(options: { const auth = username && password ? { username, password } : undefined; setEngineAuth(auth ?? null); - if (newInfo.baseUrl) { - const ok = await connectToServer( - newInfo.baseUrl, - newInfo.projectDir ?? undefined, - { reason: "workspace-openwrk-switch" }, - auth, - ); - if (!ok) { - options.setError("Failed to reconnect after workspace switch"); + if (newInfo.baseUrl) { + const ok = await connectToServer( + newInfo.baseUrl, + newInfo.projectDir ?? undefined, + { reason: "workspace-openwrk-switch" }, + auth, + { navigate: false }, + ); + if (!ok) { + options.setError("Failed to reconnect after workspace switch"); + } } - } } else { // Stop the current engine const info = await engineStop(); @@ -806,17 +855,18 @@ export function createWorkspaceStore(options: { setEngineAuth(auth ?? null); // Reconnect to server - if (newInfo.baseUrl) { - const ok = await connectToServer( - newInfo.baseUrl, - newInfo.projectDir ?? undefined, - { reason: "workspace-restart" }, - auth, - ); - if (!ok) { - options.setError("Failed to reconnect after workspace switch"); + if (newInfo.baseUrl) { + const ok = await connectToServer( + newInfo.baseUrl, + newInfo.projectDir ?? undefined, + { reason: "workspace-restart" }, + auth, + { navigate: false }, + ); + if (!ok) { + options.setError("Failed to reconnect after workspace switch"); + } } - } } } catch (e) { const message = e instanceof Error ? e.message : safeStringify(e); @@ -847,7 +897,7 @@ export function createWorkspaceStore(options: { reason?: string; }, auth?: OpencodeAuth, - connectOptions?: { quiet?: boolean }, + connectOptions?: { quiet?: boolean; navigate?: boolean }, ) { console.log("[workspace] connect", { baseUrl: nextBaseUrl, @@ -855,6 +905,7 @@ export function createWorkspaceStore(options: { workspaceType: context?.workspaceType ?? null, }); const quiet = connectOptions?.quiet ?? false; + const navigate = connectOptions?.navigate ?? true; options.setError(null); if (!quiet) { options.setBusy(true); @@ -936,7 +987,7 @@ export function createWorkspaceStore(options: { options.refreshSkills({ force: true }).catch(() => undefined); options.refreshPlugins().catch(() => undefined); - if (!options.selectedSessionId()) { + if (navigate && !options.selectedSessionId()) { options.setTab("scheduled"); options.setView("session"); } @@ -1484,7 +1535,7 @@ export function createWorkspaceStore(options: { } } - async function startHost(optionsOverride?: { workspacePath?: string }) { + async function startHost(optionsOverride?: { workspacePath?: string; navigate?: boolean }) { if (!isTauriRuntime()) { options.setError(t("app.error.tauri_required", currentLocale())); return false; @@ -1552,15 +1603,16 @@ export function createWorkspaceStore(options: { const auth = username && password ? { username, password } : undefined; setEngineAuth(auth ?? null); - if (info.baseUrl) { - const ok = await connectToServer( - info.baseUrl, - info.projectDir ?? undefined, - { reason: "host-start" }, - auth, - ); - if (!ok) return false; - } + if (info.baseUrl) { + const ok = await connectToServer( + info.baseUrl, + info.projectDir ?? undefined, + { reason: "host-start" }, + auth, + { navigate: optionsOverride?.navigate ?? true }, + ); + if (!ok) return false; + } markOnboardingComplete(); return true;