From b2a006f338934483be4e447545f8d89e1bc4af1d Mon Sep 17 00:00:00 2001 From: ben Date: Fri, 6 Feb 2026 23:40:40 -0800 Subject: [PATCH] fix(workspace): avoid UI stalls when switching (#489) --- packages/app/src/app/app.tsx | 26 ++++++++++++++--------- packages/app/src/app/context/workspace.ts | 6 ++++++ 2 files changed, 22 insertions(+), 10 deletions(-) diff --git a/packages/app/src/app/app.tsx b/packages/app/src/app/app.tsx index bc63f2a90..401230be1 100644 --- a/packages/app/src/app/app.tsx +++ b/packages/app/src/app/app.tsx @@ -65,6 +65,7 @@ import type { ResetOpenworkMode, SettingsTab, SkillCard, + SidebarSessionItem, TodoItem, View, WorkspaceSessionGroup, @@ -1426,7 +1427,7 @@ export default function App() { type SidebarWorkspaceSessionsStatus = WorkspaceSessionGroup["status"]; const [sidebarSessionsByWorkspaceId, setSidebarSessionsByWorkspaceId] = createSignal< - Record + Record >({}); const [sidebarSessionStatusByWorkspaceId, setSidebarSessionStatusByWorkspaceId] = createSignal< Record @@ -1438,7 +1439,7 @@ export default function App() { const pruneSidebarSessionState = (workspaceIds: Set) => { setSidebarSessionsByWorkspaceId((prev) => { let changed = false; - const next: Record = {}; + const next: Record = {}; for (const [id, list] of Object.entries(prev)) { if (!workspaceIds.has(id)) { changed = true; @@ -1560,9 +1561,18 @@ export default function App() { ? list.filter((session) => normalizeDirectoryPath(session.directory) === root) : list; + const sorted = sortSessionsByActivity(filtered); + const items: SidebarSessionItem[] = sorted.map((session) => ({ + id: session.id, + title: session.title, + slug: session.slug, + time: session.time, + directory: session.directory, + })); + setSidebarSessionsByWorkspaceId((prev) => ({ ...prev, - [id]: sortSessionsByActivity(filtered), + [id]: items, })); setSidebarSessionStatusByWorkspaceId((prev) => ({ ...prev, [id]: "ready" })); } catch (error) { @@ -1582,6 +1592,8 @@ export default function App() { : list; for (const ws of ordered) { await refreshSidebarWorkspaceSessions(ws.id); + // Yield so long refresh passes don't block UI / timers. + await new Promise((resolve) => setTimeout(resolve, 0)); } }; @@ -1628,13 +1640,7 @@ export default function App() { const groupSessions = sessionsById[workspace.id] ?? []; return { workspace, - sessions: groupSessions.map((session) => ({ - id: session.id, - title: session.title, - slug: session.slug, - time: session.time, - directory: session.directory, - })), + sessions: groupSessions, status: statusById[workspace.id] ?? "idle", error: errorById[workspace.id] ?? null, }; diff --git a/packages/app/src/app/context/workspace.ts b/packages/app/src/app/context/workspace.ts index 900a092b0..61519ba4e 100644 --- a/packages/app/src/app/context/workspace.ts +++ b/packages/app/src/app/context/workspace.ts @@ -515,6 +515,12 @@ export function createWorkspaceStore(options: { setConnectingWorkspaceId(id); updateWorkspaceConnectionState(id, { status: "connecting", message: null }); + // Allow the UI to paint the "switching" state before we kick off work that can + // trigger expensive reactive updates (e.g. sidebar session refreshes). + if (typeof window !== "undefined" && typeof window.requestAnimationFrame === "function") { + await new Promise((resolve) => window.requestAnimationFrame(() => resolve())); + } + try { if (isRemote) { options.setStartupPreference("server");