From 89cc1acff2e5b354a6fe4b0a7c2e7b14cbb951a4 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Sun, 31 May 2026 08:06:32 +0000 Subject: [PATCH] Fix project context eviction during in-flight project open MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit rootsBoundToWindows() only counted bound window/tab roots. During a slow project open (init finished but window not yet rebound), background rebalance could treat the new context as idle and close it — breaking the open flow. Include pending window roots (from #453) and in-flight init promise keys in the active-root set used for rebalance retention. Co-authored-by: Arul Sharma --- apps/desktop/src/main/main.ts | 18 ++++++------- .../projects/projectContextRoots.test.ts | 27 +++++++++++++++++++ .../services/projects/projectContextRoots.ts | 25 +++++++++++++++++ 3 files changed, 60 insertions(+), 10 deletions(-) create mode 100644 apps/desktop/src/main/services/projects/projectContextRoots.test.ts create mode 100644 apps/desktop/src/main/services/projects/projectContextRoots.ts diff --git a/apps/desktop/src/main/main.ts b/apps/desktop/src/main/main.ts index a3c24c742..9d6a59a25 100644 --- a/apps/desktop/src/main/main.ts +++ b/apps/desktop/src/main/main.ts @@ -78,6 +78,7 @@ import { import { inspectRecentProject, type RecentProjectInspection } from "./services/projects/recentProjectSummary"; import { resolveProjectIcon } from "./services/projects/projectIconResolver"; import { normalizeStartupProjectState, resolveStartupProject } from "./services/projects/startupProjectResolver"; +import { collectRootsBoundToWindows } from "./services/projects/projectContextRoots"; import { createAdeProjectService } from "./services/projects/adeProjectService"; import { createConfigReloadService } from "./services/projects/configReloadService"; import { IPC } from "../shared/ipc"; @@ -1191,16 +1192,13 @@ app.whenReady().then(async () => { } : null; - const rootsBoundToWindows = (): Set => { - const roots = new Set(); - for (const root of windowProjectRoots.values()) { - if (root) roots.add(root); - } - for (const tabRoots of windowProjectTabRoots.values()) { - for (const root of tabRoots) roots.add(root); - } - return roots; - }; + const rootsBoundToWindows = (): Set => + collectRootsBoundToWindows({ + windowProjectRoots: windowProjectRoots.values(), + windowProjectTabRoots: windowProjectTabRoots.values(), + windowPendingProjectRoots: windowPendingProjectRoots.values(), + projectInitPromises: projectInitPromises.keys(), + }); const emitProjectChangedToWindow = ( windowId: number | null, diff --git a/apps/desktop/src/main/services/projects/projectContextRoots.test.ts b/apps/desktop/src/main/services/projects/projectContextRoots.test.ts new file mode 100644 index 000000000..f60c58c03 --- /dev/null +++ b/apps/desktop/src/main/services/projects/projectContextRoots.test.ts @@ -0,0 +1,27 @@ +import { describe, expect, it } from "vitest"; +import { collectRootsBoundToWindows } from "./projectContextRoots"; + +describe("collectRootsBoundToWindows", () => { + it("includes pending and in-flight init roots so rebalance cannot evict mid-open", () => { + const roots = collectRootsBoundToWindows({ + windowProjectRoots: ["/old-repo"], + windowProjectTabRoots: [new Set(["/old-repo"])], + windowPendingProjectRoots: [new Map([["/new-repo", 1]])], + projectInitPromises: ["/warming-repo"], + }); + + expect([...roots].sort()).toEqual(["/new-repo", "/old-repo", "/warming-repo"].sort()); + }); + + it("deduplicates the same root across binding sources", () => { + const roots = collectRootsBoundToWindows({ + windowProjectRoots: ["/repo"], + windowProjectTabRoots: [new Set(["/repo"])], + windowPendingProjectRoots: [new Map([["/repo", 2]])], + projectInitPromises: ["/repo"], + }); + + expect(roots.size).toBe(1); + expect(roots.has("/repo")).toBe(true); + }); +}); diff --git a/apps/desktop/src/main/services/projects/projectContextRoots.ts b/apps/desktop/src/main/services/projects/projectContextRoots.ts new file mode 100644 index 000000000..3adfbfb25 --- /dev/null +++ b/apps/desktop/src/main/services/projects/projectContextRoots.ts @@ -0,0 +1,25 @@ +/** + * Roots that must be treated as "in use" for project-context retention and rebalance. + * Includes window bindings plus in-flight opens (pending IPC authorization and init promises). + */ +export function collectRootsBoundToWindows(args: { + windowProjectRoots: Iterable; + windowProjectTabRoots: Iterable>; + windowPendingProjectRoots: Iterable>; + projectInitPromises: Iterable; +}): Set { + const roots = new Set(); + for (const root of args.windowProjectRoots) { + if (root) roots.add(root); + } + for (const tabRoots of args.windowProjectTabRoots) { + for (const root of tabRoots) roots.add(root); + } + for (const pendingRoots of args.windowPendingProjectRoots) { + for (const root of pendingRoots.keys()) roots.add(root); + } + for (const root of args.projectInitPromises) { + roots.add(root); + } + return roots; +}