Keep projects visible after archiving workspaces (#1631)

This commit is contained in:
Mohamed Boudra
2026-06-20 20:20:30 +08:00
committed by GitHub
parent 80fc11541f
commit b3661193af
2 changed files with 86 additions and 2 deletions
@@ -16,7 +16,11 @@ import {
workspaceEqualityFns,
type SidebarOrderSnapshot,
} from "./selectors";
import { useSessionStore, type WorkspaceDescriptor } from "../session-store";
import {
useSessionStore,
type EmptyProjectDescriptor,
type WorkspaceDescriptor,
} from "../session-store";
const SERVER_ID = "test-server";
@@ -87,6 +91,12 @@ function emptySidebarOrder(): SidebarOrderSnapshot {
};
}
function selectWorkspaceStructureProjectKeys(
state: Parameters<typeof selectWorkspaceStructureProjects>[0],
): string[] {
return selectWorkspaceStructureProjects(state, SERVER_ID).map((project) => project.projectKey);
}
afterEach(() => {
useSessionStore.getState().clearSession(SERVER_ID);
});
@@ -208,6 +218,38 @@ describe("workspace structure composition", () => {
});
}
it("keeps a project parent visible throughout the last workspace archive transition", () => {
const workspace = createWorkspace({
id: "workspace-a",
projectId: "project-a",
projectDisplayName: "Project A",
projectRootPath: "/repo/a",
workspaceDirectory: "/repo/a",
});
const emptyProject: EmptyProjectDescriptor = {
projectId: "project-a",
projectDisplayName: "Project A",
projectCustomName: null,
projectRootPath: "/repo/a",
projectKind: "git",
};
initializeWorkspaces([workspace]);
const emittedProjectKeys = [selectWorkspaceStructureProjectKeys(useSessionStore.getState())];
const stop = useSessionStore.subscribe((state) => {
emittedProjectKeys.push(selectWorkspaceStructureProjectKeys(state));
});
try {
useSessionStore.getState().removeWorkspace(SERVER_ID, workspace.id);
useSessionStore.getState().addEmptyProject(SERVER_ID, emptyProject);
} finally {
stop();
}
expect(emittedProjectKeys).toEqual([["project-a"], ["project-a"]]);
});
it("changes for membership updates but not status-only updates", () => {
const workspaceA = createWorkspace({ id: "workspace-a", name: "A" });
const workspaceB = createWorkspace({ id: "workspace-b", name: "B" });
+43 -1
View File
@@ -229,6 +229,30 @@ function preserveWorkspaceMapIdentity(
return changed ? next : existing;
}
function emptyProjectDescriptorFromWorkspace(
workspace: WorkspaceDescriptor,
): EmptyProjectDescriptor {
return {
projectId: workspace.projectId,
projectDisplayName: workspace.projectDisplayName,
projectCustomName: workspace.projectCustomName ?? null,
projectRootPath: workspace.projectRootPath,
projectKind: workspace.projectKind,
};
}
function hasWorkspaceInProject(
workspaces: ReadonlyMap<string, WorkspaceDescriptor>,
projectId: string,
): boolean {
for (const workspace of workspaces.values()) {
if (workspace.projectId === projectId) {
return true;
}
}
return false;
}
export type ExplorerEntryKind = "file" | "directory";
export type ExplorerFileKind = "text" | "image" | "binary";
export type ExplorerEncoding = "utf-8" | "base64" | "none";
@@ -1393,13 +1417,31 @@ export const useSessionStore = create<SessionStore>()(
if (!session || !workspaceKey) {
return prev;
}
const removedWorkspace = session.workspaces.get(workspaceKey);
if (!removedWorkspace) {
return prev;
}
const next = new Map(session.workspaces);
next.delete(workspaceKey);
let nextEmptyProjects = session.emptyProjects;
if (hasWorkspaceInProject(next, removedWorkspace.projectId)) {
if (nextEmptyProjects.has(removedWorkspace.projectId)) {
nextEmptyProjects = new Map(nextEmptyProjects);
nextEmptyProjects.delete(removedWorkspace.projectId);
}
} else {
const emptyProject = emptyProjectDescriptorFromWorkspace(removedWorkspace);
const existing = nextEmptyProjects.get(emptyProject.projectId);
if (!existing || !equal(existing, emptyProject)) {
nextEmptyProjects = new Map(nextEmptyProjects);
nextEmptyProjects.set(emptyProject.projectId, emptyProject);
}
}
return {
...prev,
sessions: {
...prev.sessions,
[serverId]: { ...session, workspaces: next },
[serverId]: { ...session, workspaces: next, emptyProjects: nextEmptyProjects },
},
};
});