fix: route folder deletion through runtime cleanup

This commit is contained in:
Jinwoo-H
2026-09-01 14:08:49 -04:00
parent 66cb43d4a4
commit 87f160dafa
6 changed files with 68 additions and 17 deletions
+7 -2
View File
@@ -13,8 +13,13 @@ import { registerRepoFolderPickerHandlers } from './repos/repo-folder-picker-han
import { registerRepoCloneHandlers } from './repos/repo-clone-lifecycle'
import { registerRepoGitUsernameHandler } from './repos/repo-git-username-handler'
import { registerBaseRefQueryHandlers } from './repos/base-ref-query-handlers'
import type { OrcaRuntimeService } from '../runtime/orca-runtime'
export function registerRepoHandlers(mainWindow: BrowserWindow, store: Store): void {
export function registerRepoHandlers(
mainWindow: BrowserWindow,
store: Store,
runtime?: OrcaRuntimeService
): void {
// Remove previously registered handlers so we can re-register on macOS app re-activation (new window).
ipcMain.removeHandler('repos:list')
ipcMain.removeHandler('repos:listForExecutionHost')
@@ -67,7 +72,7 @@ export function registerRepoHandlers(mainWindow: BrowserWindow, store: Store): v
registerProjectHostSetupHandlers(mainWindow, store)
registerRepoCreationHandlers(mainWindow, store)
registerProjectGroupHandlers(mainWindow, store)
registerFolderWorkspaceHandlers(mainWindow, store)
registerFolderWorkspaceHandlers(mainWindow, store, runtime)
registerNestedRepoImportHandler(mainWindow, store)
registerRepoUpdateHandler(mainWindow, store)
registerSparsePresetHandlers(mainWindow, store)
@@ -9,6 +9,7 @@ import {
getFolderWorkspacePathStatusForPath
} from '../../project-groups/folder-workspace-path-status'
import { getSshFilesystemProvider } from '../../providers/ssh-filesystem-dispatch'
import type { OrcaRuntimeService } from '../../runtime/orca-runtime'
import { notifyReposChanged } from './repos-changed-notification'
import {
FolderWorkspaceCreateArgs,
@@ -18,7 +19,11 @@ import {
parseProjectGroupIpcArgs
} from './repo-ipc-arg-schemas'
export function registerFolderWorkspaceHandlers(mainWindow: BrowserWindow, store: Store): void {
export function registerFolderWorkspaceHandlers(
mainWindow: BrowserWindow,
store: Store,
runtime?: OrcaRuntimeService
): void {
ipcMain.handle('folderWorkspaces:list', (): FolderWorkspace[] => store.getFolderWorkspaces())
ipcMain.handle('folderWorkspaces:getPathStatus', async (_event, rawArgs: unknown) => {
@@ -107,13 +112,15 @@ export function registerFolderWorkspaceHandlers(mainWindow: BrowserWindow, store
}
)
ipcMain.handle('folderWorkspaces:delete', (_event, rawArgs: unknown): boolean => {
ipcMain.handle('folderWorkspaces:delete', async (_event, rawArgs: unknown): Promise<boolean> => {
const args = parseProjectGroupIpcArgs(
FolderWorkspaceSelectorArgs,
rawArgs,
'invalid_folder_workspace_delete_args'
)
const deleted = store.removeFolderWorkspace(args.folderWorkspaceId)
const deleted = runtime
? (await runtime.deleteFolderWorkspace(args.folderWorkspaceId)).deleted
: store.removeFolderWorkspace(args.folderWorkspaceId)
if (deleted) {
notifyReposChanged(mainWindow)
}
@@ -70,7 +70,7 @@ export function attachMainWindowServices(
}
): void {
registerAppReloadHandler(mainWindow, options?.onBeforeRendererReload)
registerRepoHandlers(mainWindow, store)
registerRepoHandlers(mainWindow, store, runtime)
// Why: repo IPC mutations must also invalidate paired clients' catalogs (#11994).
setRepoRemoteClientNotifier(runtime)
setWorktreeCatalogRemoteClientNotifier(runtime)
@@ -54,10 +54,15 @@ export function useWorktreeCardWorkspaceActions({
event.stopPropagation()
if (showDeleteQuickAction) {
if (folderWorkspaceId) {
void deleteFolderWorkspace(folderWorkspaceId).then((deleted) => {
void deleteFolderWorkspace(
folderWorkspaceId,
worktree.hostId ? { executionHostId: worktree.hostId } : undefined
).then((deleted) => {
if (
deleted &&
useAppStore.getState().activeWorktreeId === folderWorkspaceKey(folderWorkspaceId)
useAppStore.getState().activeWorktreeId === folderWorkspaceKey(folderWorkspaceId) &&
(!worktree.hostId ||
useAppStore.getState().activeWorkspaceExecutionHostId === worktree.hostId)
) {
setActiveWorktree(null)
}
@@ -72,9 +77,9 @@ export function useWorktreeCardWorkspaceActions({
[
deleteFolderWorkspace,
folderWorkspaceId,
worktree.hostId,
setActiveWorktree,
showDeleteQuickAction,
worktree.hostId,
worktree.id
]
)
@@ -47,6 +47,25 @@ describe('createWorktreeContextMenuDeleteIntent', () => {
expect(mocks.runBatchDelete).toHaveBeenCalledWith(worktrees)
})
it('preserves the folder owner host in a context-menu delete intent', () => {
const intent = createWorktreeContextMenuDeleteIntent({
worktree: {
id: 'folder:shared',
instanceId: 'runtime-instance',
hostId: 'runtime:env-owner'
},
batchDeleteWorktrees: [],
isMultiContext: false,
folderWorkspaceId: 'shared'
})
expect(intent).toEqual({
kind: 'folder',
folderWorkspaceId: 'shared',
executionHostId: 'runtime:env-owner'
})
})
})
describe('deferWorktreeContextMenuDeleteIntent', () => {
@@ -3,11 +3,12 @@ import { folderWorkspaceKey } from '../../../../shared/workspace-scope'
import { runWorktreeBatchDelete, runWorktreeDelete } from './delete-worktree-flow'
import type { WorktreeDeleteIdentity } from './worktree-delete-request'
import type { Worktree } from '../../../../shared/worktree/types'
import type { ExecutionHostId } from '../../../../shared/execution-host'
export type WorktreeContextMenuDeleteIntent =
| { kind: 'worktree'; worktree: WorktreeDeleteIdentity }
| { kind: 'batch'; worktrees: readonly WorktreeDeleteIdentity[] }
| { kind: 'folder'; folderWorkspaceId: string }
| { kind: 'folder'; folderWorkspaceId: string; executionHostId?: ExecutionHostId }
export function createWorktreeContextMenuDeleteIntent(args: {
worktree: Pick<Worktree, 'id' | 'instanceId' | 'hostId'>
@@ -26,7 +27,11 @@ export function createWorktreeContextMenuDeleteIntent(args: {
}
}
if (args.folderWorkspaceId) {
return { kind: 'folder', folderWorkspaceId: args.folderWorkspaceId }
return {
kind: 'folder',
folderWorkspaceId: args.folderWorkspaceId,
...(args.worktree.hostId ? { executionHostId: args.worktree.hostId } : {})
}
}
const { id, instanceId, hostId } = args.worktree
return { kind: 'worktree', worktree: { id, instanceId, hostId } }
@@ -45,12 +50,22 @@ export function runWorktreeContextMenuDeleteIntent(intent: WorktreeContextMenuDe
return
}
const state = useAppStore.getState()
void state.deleteFolderWorkspace(intent.folderWorkspaceId).then((deleted) => {
const current = useAppStore.getState()
if (deleted && current.activeWorktreeId === folderWorkspaceKey(intent.folderWorkspaceId)) {
current.setActiveWorktree(null)
}
})
void state
.deleteFolderWorkspace(
intent.folderWorkspaceId,
intent.executionHostId ? { executionHostId: intent.executionHostId } : undefined
)
.then((deleted) => {
const current = useAppStore.getState()
if (
deleted &&
current.activeWorktreeId === folderWorkspaceKey(intent.folderWorkspaceId) &&
(!intent.executionHostId ||
current.activeWorkspaceExecutionHostId === intent.executionHostId)
) {
current.setActiveWorktree(null)
}
})
}
export function deferWorktreeContextMenuDeleteIntent(