diff --git a/src/main/ipc/repos.ts b/src/main/ipc/repos.ts index 56e00fac46f..316d2e62967 100644 --- a/src/main/ipc/repos.ts +++ b/src/main/ipc/repos.ts @@ -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) diff --git a/src/main/ipc/repos/folder-workspace-handlers.ts b/src/main/ipc/repos/folder-workspace-handlers.ts index 3bd8caa5fd1..2e2dd57e3f7 100644 --- a/src/main/ipc/repos/folder-workspace-handlers.ts +++ b/src/main/ipc/repos/folder-workspace-handlers.ts @@ -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 => { 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) } diff --git a/src/main/window/attach-main-window-services.ts b/src/main/window/attach-main-window-services.ts index 128ff2e0e4e..ff7d84bd706 100644 --- a/src/main/window/attach-main-window-services.ts +++ b/src/main/window/attach-main-window-services.ts @@ -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) diff --git a/src/renderer/src/components/sidebar/use-worktree-card-workspace-actions.ts b/src/renderer/src/components/sidebar/use-worktree-card-workspace-actions.ts index 665227426ac..7b1349497d3 100644 --- a/src/renderer/src/components/sidebar/use-worktree-card-workspace-actions.ts +++ b/src/renderer/src/components/sidebar/use-worktree-card-workspace-actions.ts @@ -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 ] ) diff --git a/src/renderer/src/components/sidebar/worktree-context-menu-delete-intent.test.ts b/src/renderer/src/components/sidebar/worktree-context-menu-delete-intent.test.ts index 6b1a251bce8..e83bad3f440 100644 --- a/src/renderer/src/components/sidebar/worktree-context-menu-delete-intent.test.ts +++ b/src/renderer/src/components/sidebar/worktree-context-menu-delete-intent.test.ts @@ -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', () => { diff --git a/src/renderer/src/components/sidebar/worktree-context-menu-delete-intent.ts b/src/renderer/src/components/sidebar/worktree-context-menu-delete-intent.ts index d88756c4f71..4cf5cd0d93b 100644 --- a/src/renderer/src/components/sidebar/worktree-context-menu-delete-intent.ts +++ b/src/renderer/src/components/sidebar/worktree-context-menu-delete-intent.ts @@ -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 @@ -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(