Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 8 additions & 10 deletions apps/desktop/src/main/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down Expand Up @@ -1191,16 +1192,13 @@ app.whenReady().then(async () => {
}
: null;

const rootsBoundToWindows = (): Set<string> => {
const roots = new Set<string>();
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<string> =>
collectRootsBoundToWindows({
windowProjectRoots: windowProjectRoots.values(),
windowProjectTabRoots: windowProjectTabRoots.values(),
windowPendingProjectRoots: windowPendingProjectRoots.values(),
projectInitPromises: projectInitPromises.keys(),
});

const emitProjectChangedToWindow = (
windowId: number | null,
Expand Down
Original file line number Diff line number Diff line change
@@ -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);
});
});
25 changes: 25 additions & 0 deletions apps/desktop/src/main/services/projects/projectContextRoots.ts
Original file line number Diff line number Diff line change
@@ -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<string | null>;
windowProjectTabRoots: Iterable<Set<string>>;
windowPendingProjectRoots: Iterable<Map<string, number>>;
projectInitPromises: Iterable<string>;
}): Set<string> {
const roots = new Set<string>();
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;
}
Loading