- {showTitleRowPrimary &&
}
+ {showTitleRowPrimary && !cloudRow &&
}
{showDeleteQuickAction && (
(cloud)" server whose rows live on
+// `runtime:`. The cloud identity store already maps both kinds to their sandbox
+// (this desktop's runtime records, and the pairing bindings), so this reads it.
+//
+// A cloud row on the sandbox's own checkout (isMainWorktree) IS the cloud workspace, not a local
+// repo's primary checkout: Delete must destroy the sandbox, never "Remove the project".
+import { useEffect } from 'react'
+import {
+ refreshCloudWorktreeBindings,
+ sandboxIdForWorktree,
+ useCloudWorkspaceIdentities
+} from '@/components/cloud-workspaces/cloud-workspace-identity'
+import { getCloudWorkspaceSnapshot, useIsCloudWorkspace } from './orca-cloud-workspace-store'
+
+type Row = { id: string; hostId?: string | null; isMainWorktree?: boolean }
+
+let primed = false
+
+/** Loads the bindings once per app session (a menu open refreshes them for new pairings). */
+export function usePrimeCloudRowBindings(): void {
+ useEffect(() => {
+ if (!primed) {
+ primed = true
+ void refreshCloudWorktreeBindings().catch(() => undefined)
+ }
+ }, [])
+}
+
+/** A row on an Orca Cloud workspace: this desktop's own, or one it paired with. */
+export function useIsCloudHostRow(row: Row): boolean {
+ const owned = useIsCloudWorkspace(row.id)
+ const identities = useCloudWorkspaceIdentities()
+ return owned || sandboxIdForWorktree(row, identities) !== null
+}
+
+/** The cloud workspace itself (the sandbox's own checkout): its Delete destroys the sandbox. */
+export function useIsCloudPrimaryRow(row: Row): boolean {
+ return useIsCloudHostRow(row) && row.isMainWorktree === true
+}
+
+/** Non-React: the same, for the delete funnel. */
+export function isCloudPrimaryRowNow(row: Row): boolean {
+ return (
+ row.isMainWorktree === true &&
+ (getCloudWorkspaceSnapshot().ids.has(row.id) || sandboxIdForWorktree(row) !== null)
+ )
+}
+
+/** The sandbox behind a cloud row, re-reading the bindings first (a pairing may be new). */
+export async function resolveCloudRowSandboxId(row: Row): Promise {
+ await refreshCloudWorktreeBindings().catch(() => undefined)
+ return sandboxIdForWorktree(row)
+}
diff --git a/src/renderer/src/lib/orca-cloud/cloud-primary-delete-request.ts b/src/renderer/src/lib/orca-cloud/cloud-primary-delete-request.ts
new file mode 100644
index 00000000000..81a126429db
--- /dev/null
+++ b/src/renderer/src/lib/orca-cloud/cloud-primary-delete-request.ts
@@ -0,0 +1,27 @@
+// Which cloud row the cloud delete dialog is open for (CLOUD-PRIMARY-DELETE). Its own module so the
+// delete funnel can request the dialog without importing the dialog (and the Home flows it uses).
+import type { Worktree } from '../../../../shared/worktree/types'
+
+export type CloudPrimaryDeleteTarget = Pick<
+ Worktree,
+ 'id' | 'hostId' | 'displayName' | 'repoId' | 'branch'
+> &
+ Partial>
+
+let requested: CloudPrimaryDeleteTarget | null = null
+const listeners = new Set<() => void>()
+
+export function subscribeCloudPrimaryDelete(listener: () => void): () => void {
+ listeners.add(listener)
+ return () => listeners.delete(listener)
+}
+
+export function cloudPrimaryDeleteRequest(): CloudPrimaryDeleteTarget | null {
+ return requested
+}
+
+/** Opens (target) or closes (null) the cloud delete dialog. */
+export function requestCloudPrimaryDelete(target: CloudPrimaryDeleteTarget | null): void {
+ requested = target
+ listeners.forEach((listener) => listener())
+}
diff --git a/src/shared/plugins/languages/protected-copy-keys.json b/src/shared/plugins/languages/protected-copy-keys.json
index f2fad793162..741d48cb6f8 100644
--- a/src/shared/plugins/languages/protected-copy-keys.json
+++ b/src/shared/plugins/languages/protected-copy-keys.json
@@ -950,6 +950,9 @@
"localCommands.settings.workspacesTitle",
"localCommands.text.tab",
"localCommands.text.trailingSpace",
+ "orcaCloudWorkspace.deleteFailedNamed",
+ "orcaCloudWorkspace.deletedNamed",
+ "orcaCloudWorkspace.primaryDelete.noSandbox",
"plugins.frameOpenExternal.alwaysAllow",
"plugins.frameOpenExternal.cancel",
"plugins.frameOpenExternal.message",