fix(sidebar): preserve manual order during refresh (#12072)
Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
This commit is contained in:
parent
714bcbe43f
commit
3172002d71
|
|
@ -572,6 +572,55 @@ describe('fetchWorktrees', () => {
|
|||
expect(mockApi.worktrees.updateMeta).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('does not merge stale manual order over a reorder completed during refresh', async () => {
|
||||
const store = createTestStore()
|
||||
const daily = makeWorktree({
|
||||
id: 'repo1::/path/daily',
|
||||
repoId: 'repo1',
|
||||
path: '/path/daily',
|
||||
manualOrder: 20
|
||||
})
|
||||
const relay = makeWorktree({
|
||||
id: 'repo1::/path/relay',
|
||||
repoId: 'repo1',
|
||||
path: '/path/relay',
|
||||
manualOrder: 10
|
||||
})
|
||||
const refreshedDaily = { ...daily, head: 'def456' }
|
||||
const detected = makeDetectedResult('repo1', [daily, relay])
|
||||
let resolveListing!: (worktrees: Worktree[]) => void
|
||||
const listing = new Promise<Worktree[]>((resolve) => {
|
||||
resolveListing = resolve
|
||||
})
|
||||
worktreeListMock.mockReturnValueOnce(listing)
|
||||
store.setState({
|
||||
worktreesByRepo: { repo1: [daily, relay] },
|
||||
detectedWorktreesByRepo: { repo1: detected }
|
||||
} as Partial<AppState>)
|
||||
|
||||
const refresh = store.getState().fetchWorktrees('repo1')
|
||||
await vi.waitFor(() => expect(worktreeListMock).toHaveBeenCalledTimes(1))
|
||||
await store.getState().updateWorktreesMeta(
|
||||
new Map([
|
||||
[daily.id, { manualOrder: 100 }],
|
||||
[relay.id, { manualOrder: 200 }]
|
||||
])
|
||||
)
|
||||
resolveListing([refreshedDaily, relay])
|
||||
|
||||
await refresh
|
||||
|
||||
expect(store.getState().worktreesByRepo.repo1.map((worktree) => worktree.manualOrder)).toEqual([
|
||||
100, 200
|
||||
])
|
||||
expect(
|
||||
store
|
||||
.getState()
|
||||
.detectedWorktreesByRepo.repo1.worktrees.map((worktree) => worktree.manualOrder)
|
||||
).toEqual([100, 200])
|
||||
expect(store.getState().worktreesByRepo.repo1[0]?.head).toBe('def456')
|
||||
})
|
||||
|
||||
it('updates the repo entry when only the persisted base ref changes', async () => {
|
||||
const store = createTestStore()
|
||||
const existing = makeWorktree({
|
||||
|
|
|
|||
|
|
@ -2819,6 +2819,32 @@ function staleDetectedWorktreeProviderResult(
|
|||
: undefined
|
||||
}
|
||||
|
||||
function preserveConcurrentManualOrder<T extends Worktree>(
|
||||
incoming: readonly T[],
|
||||
requestStarted: readonly Worktree[] | undefined,
|
||||
current: readonly Worktree[] | undefined,
|
||||
matchesRefreshHost: (worktree: Worktree) => boolean
|
||||
): T[] {
|
||||
if (!requestStarted || !current) {
|
||||
return [...incoming]
|
||||
}
|
||||
const startedById = new Map(
|
||||
requestStarted.filter(matchesRefreshHost).map((worktree) => [worktree.id, worktree])
|
||||
)
|
||||
const currentById = new Map(
|
||||
current.filter(matchesRefreshHost).map((worktree) => [worktree.id, worktree])
|
||||
)
|
||||
return incoming.map((worktree) => {
|
||||
const started = startedById.get(worktree.id)
|
||||
const latest = currentById.get(worktree.id)
|
||||
if (!started || !latest || started.manualOrder === latest.manualOrder) {
|
||||
return worktree
|
||||
}
|
||||
// Why: a refresh response may predate a completed drag; the renderer's optimistic rank is newer.
|
||||
return { ...worktree, manualOrder: latest.manualOrder }
|
||||
})
|
||||
}
|
||||
|
||||
type FencedWorktreeMergeArgs = {
|
||||
repoId: string
|
||||
hostId: ExecutionHostId
|
||||
|
|
@ -2850,7 +2876,17 @@ function mergeFetchedWorktrees(
|
|||
}
|
||||
admitted = true
|
||||
const matchOptions = worktreeHostMatchOptions(s, args.repoId, args.hostId)
|
||||
let incoming = toVisibleWorktrees(args.refresh.result, args.hostId, args.setup)
|
||||
const currentWorktrees = s.worktreesByRepo[args.repoId]
|
||||
const refreshResult = {
|
||||
...args.refresh.result,
|
||||
worktrees: preserveConcurrentManualOrder(
|
||||
args.refresh.result.worktrees,
|
||||
args.requestStartedWorktrees,
|
||||
currentWorktrees,
|
||||
(worktree) => worktreeMatchesHost(worktree, args.hostId, matchOptions)
|
||||
)
|
||||
}
|
||||
let incoming = toVisibleWorktrees(refreshResult, args.hostId, args.setup)
|
||||
incoming = routeListingBranchSwitchesThroughGitIdentity({
|
||||
requestStarted: args.requestStartedWorktrees,
|
||||
current: s.worktreesByRepo[args.repoId],
|
||||
|
|
@ -2868,7 +2904,7 @@ function mergeFetchedWorktrees(
|
|||
)
|
||||
const mergedDetected = mergeDetectedWorktreesForHost(
|
||||
s.detectedWorktreesByRepo[args.repoId],
|
||||
args.refresh.result,
|
||||
refreshResult,
|
||||
args.hostId,
|
||||
args.setup,
|
||||
matchOptions
|
||||
|
|
|
|||
Loading…
Reference in New Issue