mirror of
https://github.com/stablyai/orca.git
synced 2026-10-30 00:27:39 +00:00
fix: contain sort order persistence failures
This commit is contained in:
@@ -202,14 +202,12 @@ import {
|
||||
pruneWorktreeSelection,
|
||||
updateWorktreeSelection
|
||||
} from './worktree-multi-selection'
|
||||
import { callRuntimeRpc } from '@/runtime/runtime-rpc-client'
|
||||
import { splitWorktreeSortOrderByHost } from '@/lib/worktree-sort-order-host-split'
|
||||
import { persistWorktreeSortOrderByHost } from '@/lib/worktree-sort-order-persistence'
|
||||
import {
|
||||
ALL_EXECUTION_HOSTS_SCOPE,
|
||||
getRepoExecutionHostId,
|
||||
getSettingsFocusedExecutionHostId,
|
||||
type ExecutionHostId,
|
||||
parseExecutionHostId
|
||||
type ExecutionHostId
|
||||
} from '../../../../shared/execution-host'
|
||||
import { getRepoHeaderCreateState } from './repo-header-create-state'
|
||||
import type { PendingSidebarRowReveal, PendingSidebarWorktreeReveal } from '@/store/slices/ui'
|
||||
@@ -5520,21 +5518,7 @@ const WorktreeList = React.memo(function WorktreeList({
|
||||
// Why: sortOrder is persisted in each host's worktreeMeta and enriched from
|
||||
// the owner host, so persist each host's ids on that host.
|
||||
const state = useAppStore.getState()
|
||||
for (const group of splitWorktreeSortOrderByHost(state, sortedIds)) {
|
||||
const parsed = parseExecutionHostId(group.hostId)
|
||||
const target =
|
||||
parsed?.kind === 'runtime'
|
||||
? ({ kind: 'environment', environmentId: parsed.environmentId } as const)
|
||||
: ({ kind: 'local' } as const)
|
||||
void (target.kind === 'environment'
|
||||
? callRuntimeRpc(
|
||||
target,
|
||||
'worktree.persistSortOrder',
|
||||
{ orderedIds: group.orderedIds },
|
||||
{ timeoutMs: 15_000 }
|
||||
)
|
||||
: window.api.worktrees.persistSortOrder({ orderedIds: group.orderedIds }))
|
||||
}
|
||||
persistWorktreeSortOrderByHost(state, sortedIds)
|
||||
}, [sortedIds, sortBy])
|
||||
|
||||
// Flatten, filter, and apply stable sort order via the shared utility so
|
||||
|
||||
@@ -0,0 +1,84 @@
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import { callRuntimeRpc } from '@/runtime/runtime-rpc-client'
|
||||
import type { WorktreeRuntimeOwnerState } from './worktree-runtime-owner'
|
||||
import { persistWorktreeSortOrderByHost } from './worktree-sort-order-persistence'
|
||||
|
||||
vi.mock('@/runtime/runtime-rpc-client', () => ({
|
||||
callRuntimeRpc: vi.fn()
|
||||
}))
|
||||
|
||||
const localPersistSortOrder = vi.fn()
|
||||
const runtimeCall = vi.mocked(callRuntimeRpc)
|
||||
const UNHANDLED_REJECTION_SETTLE_MS = 20
|
||||
|
||||
const state: WorktreeRuntimeOwnerState = {
|
||||
settings: { activeRuntimeEnvironmentId: 'focused-env' },
|
||||
repos: [
|
||||
{ id: 'local-repo', connectionId: null, executionHostId: 'local' },
|
||||
{ id: 'runtime-repo', connectionId: null, executionHostId: 'runtime:env-1' }
|
||||
],
|
||||
worktreesByRepo: {
|
||||
'local-repo': [{ id: 'local-repo::wt-a', repoId: 'local-repo' }],
|
||||
'runtime-repo': [{ id: 'runtime-repo::wt-b', repoId: 'runtime-repo' }]
|
||||
}
|
||||
}
|
||||
|
||||
async function collectUnhandledRejections(run: () => void): Promise<unknown[]> {
|
||||
const reasons: unknown[] = []
|
||||
const onUnhandledRejection = (reason: unknown): void => {
|
||||
reasons.push(reason)
|
||||
}
|
||||
|
||||
process.on('unhandledRejection', onUnhandledRejection)
|
||||
try {
|
||||
run()
|
||||
await new Promise((resolve) => setTimeout(resolve, UNHANDLED_REJECTION_SETTLE_MS))
|
||||
} finally {
|
||||
process.off('unhandledRejection', onUnhandledRejection)
|
||||
}
|
||||
|
||||
return reasons
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
runtimeCall.mockReset()
|
||||
localPersistSortOrder.mockReset()
|
||||
runtimeCall.mockResolvedValue(undefined)
|
||||
localPersistSortOrder.mockResolvedValue(undefined)
|
||||
vi.stubGlobal('window', {
|
||||
api: {
|
||||
worktrees: {
|
||||
persistSortOrder: localPersistSortOrder
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
vi.unstubAllGlobals()
|
||||
})
|
||||
|
||||
describe('persistWorktreeSortOrderByHost', () => {
|
||||
it('persists each owner host through the matching transport', () => {
|
||||
persistWorktreeSortOrderByHost(state, ['runtime-repo::wt-b', 'local-repo::wt-a'])
|
||||
|
||||
expect(runtimeCall).toHaveBeenCalledWith(
|
||||
{ kind: 'environment', environmentId: 'env-1' },
|
||||
'worktree.persistSortOrder',
|
||||
{ orderedIds: ['runtime-repo::wt-b'] },
|
||||
{ timeoutMs: 15_000 }
|
||||
)
|
||||
expect(localPersistSortOrder).toHaveBeenCalledWith({ orderedIds: ['local-repo::wt-a'] })
|
||||
})
|
||||
|
||||
it('handles best-effort persistence rejections from disconnected hosts', async () => {
|
||||
runtimeCall.mockRejectedValueOnce(new Error('SSH disconnected'))
|
||||
localPersistSortOrder.mockRejectedValueOnce(new Error('local store unavailable'))
|
||||
|
||||
const unhandledRejections = await collectUnhandledRejections(() => {
|
||||
persistWorktreeSortOrderByHost(state, ['runtime-repo::wt-b', 'local-repo::wt-a'])
|
||||
})
|
||||
|
||||
expect(unhandledRejections).toEqual([])
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,35 @@
|
||||
import { callRuntimeRpc } from '@/runtime/runtime-rpc-client'
|
||||
import { parseExecutionHostId } from '../../../shared/execution-host'
|
||||
import type { WorktreeRuntimeOwnerState } from './worktree-runtime-owner'
|
||||
import { splitWorktreeSortOrderByHost } from './worktree-sort-order-host-split'
|
||||
|
||||
function ignoreSortOrderPersistenceFailure(promise: Promise<unknown>): void {
|
||||
void promise.catch(() => {
|
||||
// Why: sort-order restore is best-effort; SSH disconnects during smart sort
|
||||
// must not surface as unhandled rejections that pollute crash diagnostics.
|
||||
})
|
||||
}
|
||||
|
||||
export function persistWorktreeSortOrderByHost(
|
||||
state: WorktreeRuntimeOwnerState,
|
||||
orderedIds: readonly string[]
|
||||
): void {
|
||||
for (const group of splitWorktreeSortOrderByHost(state, orderedIds)) {
|
||||
const parsed = parseExecutionHostId(group.hostId)
|
||||
if (parsed?.kind === 'runtime') {
|
||||
ignoreSortOrderPersistenceFailure(
|
||||
callRuntimeRpc(
|
||||
{ kind: 'environment', environmentId: parsed.environmentId },
|
||||
'worktree.persistSortOrder',
|
||||
{ orderedIds: group.orderedIds },
|
||||
{ timeoutMs: 15_000 }
|
||||
)
|
||||
)
|
||||
continue
|
||||
}
|
||||
|
||||
ignoreSortOrderPersistenceFailure(
|
||||
window.api.worktrees.persistSortOrder({ orderedIds: group.orderedIds })
|
||||
)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user