mirror of
https://github.com/stablyai/orca.git
synced 2026-10-17 08:03:14 +00:00
Route delete warnings by worktree owner
Co-authored-by: Orca <help@stably.ai>
This commit is contained in:
@@ -286,3 +286,4 @@ Done means: reviewers and future agents can understand the vision, implementatio
|
||||
- [x] 2026-06-13: Electron-verified a local project with local GitHub credentials: added the local Orca worktree to the running dev app, confirmed `gh` auth for `Jinwoo-H`, fetched 10 GitHub work items through explicit `TaskSourceContext { provider: github, hostId: local }`, verified the local source-scoped cache key, and captured the rendered Tasks page showing GitHub issues for the local project.
|
||||
- [x] 2026-06-13: Snapshotted Source Control PR-generation runtime ownership and reused that owner for generation, cancellation, and post-generation git refresh, preventing async PR generation from following a later focused-host switch. Verified with focused Source Control host-boundary tests, targeted oxlint, and `git diff --check`.
|
||||
- [x] 2026-06-13: Extended Source Control git remote operations and upstream refresh to accept explicit runtime-owner settings, then wired SourceControl and ChecksPanel to pass the repo owner host for push, pull, fetch, sync, fast-forward, rebase, publish, and PR-generation follow-up refreshes. Verified with focused editor/git-status tests, targeted oxlint, `git diff --check`, and full `pnpm run typecheck`.
|
||||
- [x] 2026-06-13: Routed Delete Workspace dirty-state preloads through the selected worktree runtime owner instead of ambient focused settings, so delete warnings inspect the host that owns the workspace. Verified with focused DeleteWorktreeDialog host-boundary tests, existing worktree-owner tests, targeted oxlint, `git diff --check`, and full `pnpm run typecheck`.
|
||||
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
import { readFileSync } from 'node:fs'
|
||||
import { join } from 'node:path'
|
||||
import { describe, expect, it } from 'vitest'
|
||||
|
||||
const SOURCE = readFileSync(join(__dirname, 'DeleteWorktreeDialog.tsx'), 'utf8')
|
||||
|
||||
function sourceBetween(source: string, startPattern: string, endPattern: string): string {
|
||||
const start = source.indexOf(startPattern)
|
||||
expect(start).toBeGreaterThanOrEqual(0)
|
||||
const end = source.indexOf(endPattern, start + startPattern.length)
|
||||
expect(end).toBeGreaterThan(start)
|
||||
return source.slice(start, end)
|
||||
}
|
||||
|
||||
describe('DeleteWorktreeDialog host-context boundaries', () => {
|
||||
it('preloads git status from the selected worktree owner instead of the focused host', () => {
|
||||
const effect = sourceBetween(
|
||||
SOURCE,
|
||||
'const statusTargets = deleteTargets.filter(',
|
||||
'return () => {'
|
||||
)
|
||||
|
||||
expect(effect).toContain('getSettingsForWorktreeRuntimeOwner')
|
||||
expect(effect).toContain('worktreesByRepo: useAppStore.getState().worktreesByRepo')
|
||||
expect(effect).toContain('item.id')
|
||||
expect(effect).not.toContain('settings,\n worktreeId: item.id')
|
||||
})
|
||||
})
|
||||
@@ -11,6 +11,7 @@ import { useAppStore } from '@/store'
|
||||
import { toast } from 'sonner'
|
||||
import { getConnectionId } from '@/lib/connection-context'
|
||||
import { getRuntimeGitStatus } from '@/runtime/runtime-git-client'
|
||||
import { getSettingsForWorktreeRuntimeOwner } from '@/lib/worktree-runtime-owner'
|
||||
import { runWorktreeDeletesInParallel } from './delete-worktree-flow'
|
||||
import { getWorkspaceDeleteLineage } from './workspace-delete-lineage'
|
||||
import { DeleteWorktreeLineageNotice } from './DeleteWorktreeLineageNotice'
|
||||
@@ -188,7 +189,12 @@ const DeleteWorktreeDialog = React.memo(function DeleteWorktreeDialog() {
|
||||
let cancelled = false
|
||||
for (const item of statusTargets) {
|
||||
void getRuntimeGitStatus({
|
||||
settings,
|
||||
// Why: delete warnings inspect git state for the selected workspace;
|
||||
// a later focused-host switch must not make this preload query another host.
|
||||
settings: getSettingsForWorktreeRuntimeOwner(
|
||||
{ repos, settings, worktreesByRepo: useAppStore.getState().worktreesByRepo },
|
||||
item.id
|
||||
),
|
||||
worktreeId: item.id,
|
||||
worktreePath: item.path,
|
||||
connectionId: getConnectionId(item.id) ?? undefined
|
||||
@@ -206,7 +212,7 @@ const DeleteWorktreeDialog = React.memo(function DeleteWorktreeDialog() {
|
||||
return () => {
|
||||
cancelled = true
|
||||
}
|
||||
}, [deleteTargets, gitStatusByWorktree, isOpen, repoMap, setGitStatus, settings])
|
||||
}, [deleteTargets, gitStatusByWorktree, isOpen, repoMap, repos, setGitStatus, settings])
|
||||
|
||||
const handleOpenChange = useCallback(
|
||||
(open: boolean) => {
|
||||
@@ -236,21 +242,33 @@ const DeleteWorktreeDialog = React.memo(function DeleteWorktreeDialog() {
|
||||
// Why: the toast confirms the preference was saved and points the user at
|
||||
// where to undo it. The "Open Settings" action deep-links to the General
|
||||
// pane so they never have to hunt for the toggle if they change their mind.
|
||||
toast.success(translate("auto.components.sidebar.DeleteWorktreeDialog.dd3a45bbbd", "We'll skip this confirmation next time."), {
|
||||
description: translate("auto.components.sidebar.DeleteWorktreeDialog.2b56b35f53", "You can change this in Settings."),
|
||||
duration: 8000,
|
||||
action: {
|
||||
label: translate("auto.components.sidebar.DeleteWorktreeDialog.5cc1a6701c", "Open Settings"),
|
||||
onClick: () => {
|
||||
openSettingsPage()
|
||||
openSettingsTarget({
|
||||
pane: 'general',
|
||||
repoId: null,
|
||||
sectionId: 'general-skip-delete-worktree-confirm'
|
||||
})
|
||||
toast.success(
|
||||
translate(
|
||||
'auto.components.sidebar.DeleteWorktreeDialog.dd3a45bbbd',
|
||||
"We'll skip this confirmation next time."
|
||||
),
|
||||
{
|
||||
description: translate(
|
||||
'auto.components.sidebar.DeleteWorktreeDialog.2b56b35f53',
|
||||
'You can change this in Settings.'
|
||||
),
|
||||
duration: 8000,
|
||||
action: {
|
||||
label: translate(
|
||||
'auto.components.sidebar.DeleteWorktreeDialog.5cc1a6701c',
|
||||
'Open Settings'
|
||||
),
|
||||
onClick: () => {
|
||||
openSettingsPage()
|
||||
openSettingsTarget({
|
||||
pane: 'general',
|
||||
repoId: null,
|
||||
sectionId: 'general-skip-delete-worktree-confirm'
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
)
|
||||
}, [openSettingsPage, openSettingsTarget, updateSettings])
|
||||
|
||||
const handleForceDeletedFromToast = useCallback(
|
||||
@@ -282,17 +300,29 @@ const DeleteWorktreeDialog = React.memo(function DeleteWorktreeDialog() {
|
||||
deletePromise
|
||||
.then((result) => {
|
||||
if (!result.ok) {
|
||||
toast.error(translate("auto.components.sidebar.DeleteWorktreeDialog.42e610d6cf", "Force delete failed"), {
|
||||
description: result.error
|
||||
})
|
||||
toast.error(
|
||||
translate(
|
||||
'auto.components.sidebar.DeleteWorktreeDialog.42e610d6cf',
|
||||
'Force delete failed'
|
||||
),
|
||||
{
|
||||
description: result.error
|
||||
}
|
||||
)
|
||||
return
|
||||
}
|
||||
onDeleted?.([worktreeId])
|
||||
})
|
||||
.catch((err: unknown) => {
|
||||
toast.error(translate("auto.components.sidebar.DeleteWorktreeDialog.4f6750ca7b", "Failed to delete workspace"), {
|
||||
description: err instanceof Error ? err.message : String(err)
|
||||
})
|
||||
toast.error(
|
||||
translate(
|
||||
'auto.components.sidebar.DeleteWorktreeDialog.4f6750ca7b',
|
||||
'Failed to delete workspace'
|
||||
),
|
||||
{
|
||||
description: err instanceof Error ? err.message : String(err)
|
||||
}
|
||||
)
|
||||
})
|
||||
} else {
|
||||
// Why: this modal is the destructive confirmation for the workspace
|
||||
@@ -365,14 +395,23 @@ const DeleteWorktreeDialog = React.memo(function DeleteWorktreeDialog() {
|
||||
>
|
||||
<DialogHeader>
|
||||
<DialogTitle className="text-sm">
|
||||
{isBatchDelete ? translate("auto.components.sidebar.DeleteWorktreeDialog.86f0ae1257", "Delete Workspaces") : translate("auto.components.sidebar.DeleteWorktreeDialog.fc23c4cbdf", "Delete Workspace")}
|
||||
{isBatchDelete
|
||||
? translate(
|
||||
'auto.components.sidebar.DeleteWorktreeDialog.86f0ae1257',
|
||||
'Delete Workspaces'
|
||||
)
|
||||
: translate(
|
||||
'auto.components.sidebar.DeleteWorktreeDialog.fc23c4cbdf',
|
||||
'Delete Workspace'
|
||||
)}
|
||||
</DialogTitle>
|
||||
<DialogDescription className="text-xs">
|
||||
{translate("auto.components.sidebar.DeleteWorktreeDialog.91492c9ad6", "Remove")}<span className={deleteCopy.targetClassName}>{deleteCopy.targetLabel}</span>
|
||||
{translate('auto.components.sidebar.DeleteWorktreeDialog.91492c9ad6', 'Remove')}
|
||||
<span className={deleteCopy.targetClassName}>{deleteCopy.targetLabel}</span>
|
||||
{canDeleteAllLineage ? (
|
||||
<>
|
||||
{' '}
|
||||
{translate("auto.components.sidebar.DeleteWorktreeDialog.ff2a74ac0e", "and")}{' '}
|
||||
{translate('auto.components.sidebar.DeleteWorktreeDialog.ff2a74ac0e', 'and')}{' '}
|
||||
<span className="font-medium text-foreground">
|
||||
{lineageDeleteCopy.childTargetLabel}
|
||||
</span>{' '}
|
||||
|
||||
Reference in New Issue
Block a user