fix(sidebar): persist projects/workspaces and add remove project menu
Stop auto-archiving workspaces when all their agents are archived — only archive when the directory no longer exists on disk. This prevents projects from disappearing when users archive their last agent. Add a kebab dropdown menu to all project rows (desktop) with a "Remove project" action that archives all workspaces in the project.
This commit is contained in:
@@ -1363,10 +1363,6 @@ export class Session {
|
||||
);
|
||||
const staleWorkspaceIds = await detectStaleWorkspaces({
|
||||
activeWorkspaces,
|
||||
agentRecords: (await this.agentStorage.list()).map((agent) => ({
|
||||
cwd: agent.cwd,
|
||||
archivedAt: agent.archivedAt ?? null,
|
||||
})),
|
||||
checkDirectoryExists: async (cwd) => {
|
||||
try {
|
||||
await stat(cwd);
|
||||
|
||||
@@ -24,7 +24,6 @@ describe("detectStaleWorkspaces", () => {
|
||||
createWorkspaceRecord("/tmp/existing"),
|
||||
createWorkspaceRecord("/tmp/missing"),
|
||||
],
|
||||
agentRecords: [],
|
||||
checkDirectoryExists,
|
||||
});
|
||||
|
||||
@@ -32,41 +31,21 @@ describe("detectStaleWorkspaces", () => {
|
||||
expect(checkDirectoryExists.mock.calls).toEqual([["/tmp/existing"], ["/tmp/missing"]]);
|
||||
});
|
||||
|
||||
test("returns workspace ids when all matching agents are archived", async () => {
|
||||
test("keeps workspaces whose directories exist even when all agents are archived", async () => {
|
||||
const staleWorkspaceIds = await detectStaleWorkspaces({
|
||||
activeWorkspaces: [createWorkspaceRecord("/tmp/repo"), createWorkspaceRecord("/tmp/other")],
|
||||
agentRecords: [
|
||||
{
|
||||
cwd: "/tmp/repo",
|
||||
archivedAt: "2026-03-02T00:00:00.000Z",
|
||||
},
|
||||
{
|
||||
cwd: "/tmp/other",
|
||||
archivedAt: null,
|
||||
},
|
||||
],
|
||||
checkDirectoryExists: async () => true,
|
||||
});
|
||||
|
||||
expect(Array.from(staleWorkspaceIds)).toEqual(["/tmp/repo"]);
|
||||
expect(Array.from(staleWorkspaceIds)).toEqual([]);
|
||||
});
|
||||
|
||||
test("keeps workspaces with no agents or at least one active agent", async () => {
|
||||
test("keeps workspaces with no agents when directory exists", async () => {
|
||||
const staleWorkspaceIds = await detectStaleWorkspaces({
|
||||
activeWorkspaces: [
|
||||
createWorkspaceRecord("/tmp/active"),
|
||||
createWorkspaceRecord("/tmp/no-agents"),
|
||||
],
|
||||
agentRecords: [
|
||||
{
|
||||
cwd: "/tmp/active",
|
||||
archivedAt: "2026-03-02T00:00:00.000Z",
|
||||
},
|
||||
{
|
||||
cwd: "/tmp/active/../active",
|
||||
archivedAt: null,
|
||||
},
|
||||
],
|
||||
checkDirectoryExists: async () => true,
|
||||
});
|
||||
|
||||
|
||||
@@ -6,14 +6,8 @@ import type { PersistedWorkspaceRecord } from "./workspace-registry.js";
|
||||
|
||||
export type PersistedProjectKind = "git" | "non_git";
|
||||
export type PersistedWorkspaceKind = "local_checkout" | "worktree" | "directory";
|
||||
export type StaleWorkspaceAgentRecord = {
|
||||
cwd: string;
|
||||
archivedAt: string | null;
|
||||
};
|
||||
|
||||
export type DetectStaleWorkspacesInput = {
|
||||
activeWorkspaces: PersistedWorkspaceRecord[];
|
||||
agentRecords: StaleWorkspaceAgentRecord[];
|
||||
checkDirectoryExists: (cwd: string) => Promise<boolean>;
|
||||
};
|
||||
|
||||
@@ -143,28 +137,11 @@ export async function detectStaleWorkspaces(
|
||||
input: DetectStaleWorkspacesInput,
|
||||
): Promise<Set<string>> {
|
||||
const staleWorkspaceIds = new Set<string>();
|
||||
const cwdsWithActiveAgents = new Set<string>();
|
||||
const cwdsWithAnyAgent = new Set<string>();
|
||||
|
||||
for (const agent of input.agentRecords) {
|
||||
const normalizedCwd = normalizeWorkspaceId(agent.cwd);
|
||||
cwdsWithAnyAgent.add(normalizedCwd);
|
||||
if (!agent.archivedAt) {
|
||||
cwdsWithActiveAgents.add(normalizedCwd);
|
||||
}
|
||||
}
|
||||
|
||||
for (const workspace of input.activeWorkspaces) {
|
||||
const dirExists = await input.checkDirectoryExists(workspace.cwd);
|
||||
if (!dirExists) {
|
||||
staleWorkspaceIds.add(workspace.workspaceId);
|
||||
continue;
|
||||
}
|
||||
|
||||
const hasAgents = cwdsWithAnyAgent.has(workspace.workspaceId);
|
||||
const hasActiveAgents = cwdsWithActiveAgents.has(workspace.workspaceId);
|
||||
if (hasAgents && !hasActiveAgents) {
|
||||
staleWorkspaceIds.add(workspace.workspaceId);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user