mirror of
https://github.com/stablyai/orca.git
synced 2026-10-22 08:03:37 +00:00
perf(renderer): keep worktree lineage map identity on no-op refresh
A host-scoped lineage refresh always spread IPC clones into new maps, so WorktreeList Object.is subscriptions missed every time even when capture, taskId, and coordinatorHandle were unchanged. Reuse structurally equal records, return the previous maps when nothing moved, and skip set() when both maps are still current so a no-op refresh is actually a no-op. Co-authored-by: Orca <help@stably.ai>
This commit is contained in:
@@ -3,7 +3,7 @@ import { afterEach, describe, expect, it, vi } from 'vitest'
|
||||
import type { HostLineageSnapshot } from '../../../shared/host-lineage-contract'
|
||||
import type { HostRepoCatalogSnapshot } from '../../../shared/host-repo-catalog-contract'
|
||||
import type { DirectSshAuthority, SshProviderEpoch } from '../../../shared/ssh-types'
|
||||
import type { Repo } from '../../../shared/types'
|
||||
import type { Repo, WorktreeLineage, WorkspaceLineage } from '../../../shared/types'
|
||||
import { folderWorkspaceKey, worktreeWorkspaceKey } from '../../../shared/workspace-scope'
|
||||
import type { AppState } from '../store/types'
|
||||
import { createDirectSshHostHydration } from './direct-ssh-host-hydration'
|
||||
@@ -40,6 +40,34 @@ function state(overrides: Record<string, unknown> = {}): AppState {
|
||||
} as unknown as AppState
|
||||
}
|
||||
|
||||
function productionLineage(worktreeId: string, parentWorktreeId: string): WorktreeLineage {
|
||||
return {
|
||||
worktreeId,
|
||||
worktreeInstanceId: `${worktreeId}-instance`,
|
||||
parentWorktreeId,
|
||||
parentWorktreeInstanceId: `${parentWorktreeId}-instance`,
|
||||
origin: 'orchestration',
|
||||
capture: { source: 'orchestration-context', confidence: 'explicit' },
|
||||
taskId: 'task-1',
|
||||
coordinatorHandle: 'coord-1',
|
||||
createdAt: 1
|
||||
}
|
||||
}
|
||||
|
||||
function productionWorkspaceLineage(childId: string, parentId: string): WorkspaceLineage {
|
||||
return {
|
||||
childWorkspaceKey: worktreeWorkspaceKey(childId),
|
||||
childInstanceId: `${childId}-instance`,
|
||||
parentWorkspaceKey: worktreeWorkspaceKey(parentId),
|
||||
parentInstanceId: `${parentId}-instance`,
|
||||
origin: 'orchestration',
|
||||
capture: { source: 'orchestration-context', confidence: 'explicit' },
|
||||
taskId: 'task-1',
|
||||
coordinatorHandle: 'coord-1',
|
||||
createdAt: 1
|
||||
}
|
||||
}
|
||||
|
||||
function hostSnapshot(
|
||||
owner: DirectSshAuthority,
|
||||
repos: ReturnType<typeof repo>[]
|
||||
@@ -341,6 +369,79 @@ describe('createDirectSshHostHydration', () => {
|
||||
})
|
||||
})
|
||||
|
||||
it('keeps lineage map identity for a cloned no-op host snapshot', async () => {
|
||||
const owner = authority()
|
||||
const hostLineage = productionLineage('a::/work', 'a::/parent')
|
||||
const foreignLineage = productionLineage('b::/work', 'b::/parent')
|
||||
const hostWorkspace = productionWorkspaceLineage('a::/work', 'a::/parent')
|
||||
const foreignWorkspace = productionWorkspaceLineage('b::/work', 'b::/parent')
|
||||
const store = createStore<AppState>(() =>
|
||||
state({
|
||||
repos: [repo('a', 'target-a'), repo('b', 'target-b')],
|
||||
worktreesByRepo: {
|
||||
a: [{ id: 'a::/work', repoId: 'a', hostId: 'ssh:target-a' }],
|
||||
b: [{ id: 'b::/work', repoId: 'b', hostId: 'ssh:target-b' }]
|
||||
},
|
||||
worktreeLineageById: {
|
||||
'a::/work': hostLineage,
|
||||
'b::/work': foreignLineage
|
||||
},
|
||||
workspaceLineageByChildKey: {
|
||||
[worktreeWorkspaceKey('a::/work')]: hostWorkspace,
|
||||
[worktreeWorkspaceKey('b::/work')]: foreignWorkspace
|
||||
}
|
||||
})
|
||||
)
|
||||
const snapshot: HostLineageSnapshot = {
|
||||
authoritative: true,
|
||||
authority: {
|
||||
kind: 'direct-ssh',
|
||||
executionHostId: 'ssh:target-a',
|
||||
...owner
|
||||
},
|
||||
worktreeLineageById: {
|
||||
'a::/work': hostLineage
|
||||
},
|
||||
workspaceLineageByChildKey: {
|
||||
[worktreeWorkspaceKey('a::/work')]: hostWorkspace
|
||||
}
|
||||
}
|
||||
let publications = 0
|
||||
store.subscribe(() => {
|
||||
publications += 1
|
||||
})
|
||||
const hydration = createDirectSshHostHydration({
|
||||
store,
|
||||
listRepos: vi.fn(),
|
||||
listLineage: vi.fn(async () => structuredClone(snapshot)),
|
||||
isCurrentAuthority: () => true
|
||||
})
|
||||
const beforeLineage = store.getState().worktreeLineageById
|
||||
const beforeWorkspace = store.getState().workspaceLineageByChildKey
|
||||
|
||||
await expect(
|
||||
hydration.readHostScopedLineage({
|
||||
...owner,
|
||||
catalogRevision: 0,
|
||||
repoRefs: [{ repoId: 'a', executionHostId: 'ssh:target-a' }],
|
||||
authorityRequirement: 'required',
|
||||
reason: 'reconnect'
|
||||
})
|
||||
).resolves.toBe('complete')
|
||||
|
||||
expect(store.getState().worktreeLineageById).toBe(beforeLineage)
|
||||
expect(store.getState().workspaceLineageByChildKey).toBe(beforeWorkspace)
|
||||
expect(store.getState().worktreeLineageById['a::/work']).toBe(hostLineage)
|
||||
expect(store.getState().worktreeLineageById['b::/work']).toBe(foreignLineage)
|
||||
expect(store.getState().workspaceLineageByChildKey[worktreeWorkspaceKey('a::/work')]).toBe(
|
||||
hostWorkspace
|
||||
)
|
||||
expect(store.getState().workspaceLineageByChildKey[worktreeWorkspaceKey('b::/work')]).toBe(
|
||||
foreignWorkspace
|
||||
)
|
||||
expect(publications).toBe(0)
|
||||
})
|
||||
|
||||
it('rejects lineage captured before a newer same-authority catalog revision', async () => {
|
||||
const owner = authority()
|
||||
const store = createStore<AppState>(() =>
|
||||
|
||||
@@ -5,6 +5,7 @@ import type { HostRepoCatalogSnapshot } from '../../../shared/host-repo-catalog-
|
||||
import type { DirectSshAuthority } from '../../../shared/ssh-types'
|
||||
import { isWorkspaceKey } from '../../../shared/workspace-scope'
|
||||
import type { AppState } from '../store/types'
|
||||
import { reuseEqualRecordMap } from '../store/slices/repo-identity-reconcile'
|
||||
import type {
|
||||
DirectSshLineageOutcome,
|
||||
DirectSshPreparationInput,
|
||||
@@ -78,37 +79,43 @@ function mergeExactHostLineage(
|
||||
catalogRevision: number
|
||||
): AppState {
|
||||
const scope = directSshHostHydrationScope(state, authority, catalogRevision)
|
||||
const worktreeLineageById = Object.fromEntries(
|
||||
Object.entries(state.worktreeLineageById).filter(
|
||||
([worktreeId]) => !scope.gitWorktreeIds.has(worktreeId)
|
||||
)
|
||||
)
|
||||
const workspaceLineageByChildKey = Object.fromEntries(
|
||||
Object.entries(state.workspaceLineageByChildKey).filter(
|
||||
([childKey]) => !isWorkspaceKey(childKey) || !scope.lineageWorkspaceKeys.has(childKey)
|
||||
)
|
||||
)
|
||||
const incomingWorktreeLineage = Object.fromEntries(
|
||||
Object.entries(snapshot.worktreeLineageById).filter(([worktreeId]) =>
|
||||
scope.gitWorktreeIds.has(worktreeId)
|
||||
)
|
||||
)
|
||||
const incomingWorkspaceLineage = Object.fromEntries(
|
||||
Object.entries(snapshot.workspaceLineageByChildKey).filter(
|
||||
([workspaceKey]) =>
|
||||
isWorkspaceKey(workspaceKey) && scope.lineageWorkspaceKeys.has(workspaceKey)
|
||||
)
|
||||
const nextWorktreeLineageById: AppState['worktreeLineageById'] = {}
|
||||
for (const [worktreeId, existing] of Object.entries(state.worktreeLineageById)) {
|
||||
if (!scope.gitWorktreeIds.has(worktreeId)) {
|
||||
nextWorktreeLineageById[worktreeId] = existing
|
||||
}
|
||||
}
|
||||
for (const [worktreeId, incoming] of Object.entries(snapshot.worktreeLineageById)) {
|
||||
if (scope.gitWorktreeIds.has(worktreeId)) {
|
||||
nextWorktreeLineageById[worktreeId] = incoming
|
||||
}
|
||||
}
|
||||
const nextWorkspaceLineageByChildKey: AppState['workspaceLineageByChildKey'] = {}
|
||||
for (const [childKey, existing] of Object.entries(state.workspaceLineageByChildKey)) {
|
||||
if (!isWorkspaceKey(childKey) || !scope.lineageWorkspaceKeys.has(childKey)) {
|
||||
nextWorkspaceLineageByChildKey[childKey] = existing
|
||||
}
|
||||
}
|
||||
for (const [workspaceKey, incoming] of Object.entries(snapshot.workspaceLineageByChildKey)) {
|
||||
if (isWorkspaceKey(workspaceKey) && scope.lineageWorkspaceKeys.has(workspaceKey)) {
|
||||
nextWorkspaceLineageByChildKey[workspaceKey] = incoming
|
||||
}
|
||||
}
|
||||
const worktreeLineageById = reuseEqualRecordMap(state.worktreeLineageById, nextWorktreeLineageById)
|
||||
const workspaceLineageByChildKey = reuseEqualRecordMap(
|
||||
state.workspaceLineageByChildKey,
|
||||
nextWorkspaceLineageByChildKey
|
||||
)
|
||||
if (
|
||||
worktreeLineageById === state.worktreeLineageById &&
|
||||
workspaceLineageByChildKey === state.workspaceLineageByChildKey
|
||||
) {
|
||||
return state
|
||||
}
|
||||
return {
|
||||
...state,
|
||||
worktreeLineageById: {
|
||||
...worktreeLineageById,
|
||||
...incomingWorktreeLineage
|
||||
},
|
||||
workspaceLineageByChildKey: {
|
||||
...workspaceLineageByChildKey,
|
||||
...incomingWorkspaceLineage
|
||||
}
|
||||
worktreeLineageById,
|
||||
workspaceLineageByChildKey
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -79,3 +79,35 @@ export function reconcileFetchedRepos(
|
||||
): readonly Repo[] {
|
||||
return reconcileCatalogRows(previous, next, getRepoHostIdentity)
|
||||
}
|
||||
|
||||
/**
|
||||
* Reuses equal record values from `previous` — and the whole map when nothing
|
||||
* changed — so a cloned no-op refresh leaves Object.is subscribers untouched.
|
||||
*/
|
||||
export function reuseEqualRecordMap<T>(
|
||||
previous: Record<string, T>,
|
||||
next: Record<string, T>
|
||||
): Record<string, T> {
|
||||
const nextKeys = Object.keys(next)
|
||||
const previousKeys = Object.keys(previous)
|
||||
let identical = nextKeys.length === previousKeys.length
|
||||
const reconciled: Record<string, T> = {}
|
||||
for (const key of nextKeys) {
|
||||
const existing = Object.hasOwn(previous, key) ? previous[key] : undefined
|
||||
if (existing !== undefined && areValuesEqual(existing, next[key])) {
|
||||
reconciled[key] = existing
|
||||
continue
|
||||
}
|
||||
identical = false
|
||||
reconciled[key] = next[key]
|
||||
}
|
||||
if (identical) {
|
||||
for (const key of previousKeys) {
|
||||
if (!Object.hasOwn(next, key)) {
|
||||
identical = false
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
return identical ? previous : reconciled
|
||||
}
|
||||
|
||||
@@ -3187,6 +3187,71 @@ describe('fetchWorktrees', () => {
|
||||
expect(store.getState().sortEpoch).toBe(7)
|
||||
})
|
||||
|
||||
it('keeps remote lineage map identity when a cloned payload is unchanged', async () => {
|
||||
const store = createTestStore()
|
||||
const worktree = makeWorktree({
|
||||
id: 'repo1::/remote/wt1',
|
||||
repoId: 'repo1',
|
||||
path: '/remote/wt1',
|
||||
branch: 'refs/heads/remote',
|
||||
hostId: 'runtime:env-1'
|
||||
})
|
||||
const lineage = makeLineage({
|
||||
worktreeId: worktree.id,
|
||||
parentWorktreeId: 'repo1::/remote/parent',
|
||||
capture: { source: 'orchestration-context', confidence: 'explicit' },
|
||||
taskId: 'task-42',
|
||||
coordinatorHandle: 'coord-1'
|
||||
})
|
||||
const workspaceLineage = makeWorkspaceLineage({
|
||||
childWorkspaceKey: worktreeWorkspaceKey(worktree.id),
|
||||
parentWorkspaceKey: worktreeWorkspaceKey(lineage.parentWorktreeId),
|
||||
childInstanceId: lineage.worktreeInstanceId,
|
||||
parentInstanceId: lineage.parentWorktreeInstanceId,
|
||||
origin: lineage.origin,
|
||||
capture: lineage.capture,
|
||||
taskId: lineage.taskId,
|
||||
coordinatorHandle: lineage.coordinatorHandle,
|
||||
createdAt: lineage.createdAt
|
||||
})
|
||||
const detected = makeDetectedResult('repo1', [worktree])
|
||||
store.setState({
|
||||
settings: { activeRuntimeEnvironmentId: 'env-1' } as never,
|
||||
worktreesByRepo: { repo1: [worktree] },
|
||||
detectedWorktreesByRepo: { repo1: detected },
|
||||
worktreeLineageById: { [lineage.worktreeId]: lineage },
|
||||
workspaceLineageByChildKey: {
|
||||
[workspaceLineage.childWorkspaceKey]: workspaceLineage
|
||||
},
|
||||
sortEpoch: 7
|
||||
} as Partial<AppState>)
|
||||
runtimeEnvironmentCall.mockImplementation(({ method }: RuntimeEnvironmentCallRequest) => {
|
||||
const result =
|
||||
method === 'worktree.lineageList'
|
||||
? structuredClone({
|
||||
lineage: { [lineage.worktreeId]: lineage },
|
||||
workspaceLineage: { [workspaceLineage.childWorkspaceKey]: workspaceLineage }
|
||||
})
|
||||
: structuredClone(detected)
|
||||
return Promise.resolve({
|
||||
id: 'rpc-1',
|
||||
ok: true,
|
||||
result,
|
||||
_meta: { runtimeId: 'runtime-remote' }
|
||||
})
|
||||
})
|
||||
const before = store.getState()
|
||||
|
||||
await store.getState().fetchWorktrees('repo1')
|
||||
|
||||
expect(store.getState().worktreeLineageById).toBe(before.worktreeLineageById)
|
||||
expect(store.getState().workspaceLineageByChildKey).toBe(before.workspaceLineageByChildKey)
|
||||
expect(store.getState().worktreeLineageById[lineage.worktreeId]).toBe(lineage)
|
||||
expect(store.getState().workspaceLineageByChildKey[workspaceLineage.childWorkspaceKey]).toBe(
|
||||
workspaceLineage
|
||||
)
|
||||
})
|
||||
|
||||
it('defers remote lineage when a caller owns the final host refresh', async () => {
|
||||
const store = createTestStore()
|
||||
const worktree = makeWorktree({
|
||||
@@ -3319,6 +3384,135 @@ describe('worktree lineage state', () => {
|
||||
})
|
||||
})
|
||||
|
||||
it('keeps lineage map and entry identity across a cloned no-op refresh', async () => {
|
||||
const lineage = makeLineage({
|
||||
capture: { source: 'orchestration-context', confidence: 'explicit' },
|
||||
taskId: 'task-42',
|
||||
coordinatorHandle: 'coord-1'
|
||||
})
|
||||
const workspaceLineage = makeWorkspaceLineage({
|
||||
childWorkspaceKey: worktreeWorkspaceKey(lineage.worktreeId),
|
||||
parentWorkspaceKey: worktreeWorkspaceKey(lineage.parentWorktreeId),
|
||||
childInstanceId: lineage.worktreeInstanceId,
|
||||
parentInstanceId: lineage.parentWorktreeInstanceId,
|
||||
origin: lineage.origin,
|
||||
capture: lineage.capture,
|
||||
taskId: lineage.taskId,
|
||||
coordinatorHandle: lineage.coordinatorHandle,
|
||||
createdAt: lineage.createdAt
|
||||
})
|
||||
const store = createLocalLineageTestStore(lineage)
|
||||
const payload = {
|
||||
lineage: { [lineage.worktreeId]: lineage },
|
||||
workspaceLineage: { [workspaceLineage.childWorkspaceKey]: workspaceLineage }
|
||||
}
|
||||
mockApi.worktrees.listLineage.mockImplementation(async () => structuredClone(payload))
|
||||
|
||||
await store.getState().fetchWorktreeLineage()
|
||||
const afterFirst = store.getState()
|
||||
const lineageById = afterFirst.worktreeLineageById
|
||||
const workspaceByKey = afterFirst.workspaceLineageByChildKey
|
||||
const lineageEntry = lineageById[lineage.worktreeId]
|
||||
const workspaceEntry = workspaceByKey[workspaceLineage.childWorkspaceKey]
|
||||
expect(lineageEntry).toEqual(lineage)
|
||||
expect(lineageEntry).not.toBe(lineage)
|
||||
const subscriber = vi.fn()
|
||||
const unsubscribe = store.subscribe(subscriber)
|
||||
|
||||
await store.getState().fetchWorktreeLineage()
|
||||
unsubscribe()
|
||||
|
||||
expect(store.getState().worktreeLineageById).toBe(lineageById)
|
||||
expect(store.getState().workspaceLineageByChildKey).toBe(workspaceByKey)
|
||||
expect(store.getState().worktreeLineageById[lineage.worktreeId]).toBe(lineageEntry)
|
||||
expect(store.getState().workspaceLineageByChildKey[workspaceLineage.childWorkspaceKey]).toBe(
|
||||
workspaceEntry
|
||||
)
|
||||
expect(subscriber).not.toHaveBeenCalled()
|
||||
expect(mockApi.worktrees.listLineage).toHaveBeenCalledTimes(2)
|
||||
})
|
||||
|
||||
it('preserves other-host lineage row identity across a cloned same-host refresh', async () => {
|
||||
const store = createTestStore()
|
||||
const localWorktree = makeWorktree({
|
||||
id: 'repo1::/local/child',
|
||||
repoId: 'repo1',
|
||||
hostId: LOCAL_EXECUTION_HOST_ID
|
||||
})
|
||||
const sshWorktree = makeWorktree({
|
||||
id: 'repo2::/ssh/child',
|
||||
repoId: 'repo2',
|
||||
hostId: 'ssh:ssh-1'
|
||||
})
|
||||
const localLineage = makeLineage({
|
||||
worktreeId: localWorktree.id,
|
||||
parentWorktreeId: 'repo1::/local/parent',
|
||||
capture: { source: 'orchestration-context', confidence: 'explicit' },
|
||||
taskId: 'task-local',
|
||||
coordinatorHandle: 'coord-local'
|
||||
})
|
||||
const sshLineage = makeLineage({
|
||||
worktreeId: sshWorktree.id,
|
||||
parentWorktreeId: 'repo2::/ssh/parent',
|
||||
capture: { source: 'cwd-context', confidence: 'inferred' },
|
||||
taskId: 'task-ssh',
|
||||
coordinatorHandle: 'coord-ssh'
|
||||
})
|
||||
const localWorkspace = makeWorkspaceLineage({
|
||||
childWorkspaceKey: worktreeWorkspaceKey(localWorktree.id),
|
||||
parentWorkspaceKey: worktreeWorkspaceKey(localLineage.parentWorktreeId),
|
||||
childInstanceId: localLineage.worktreeInstanceId,
|
||||
parentInstanceId: localLineage.parentWorktreeInstanceId,
|
||||
origin: localLineage.origin,
|
||||
capture: localLineage.capture,
|
||||
taskId: localLineage.taskId,
|
||||
coordinatorHandle: localLineage.coordinatorHandle
|
||||
})
|
||||
const sshWorkspace = makeWorkspaceLineage({
|
||||
childWorkspaceKey: worktreeWorkspaceKey(sshWorktree.id),
|
||||
parentWorkspaceKey: worktreeWorkspaceKey(sshLineage.parentWorktreeId),
|
||||
childInstanceId: sshLineage.worktreeInstanceId,
|
||||
parentInstanceId: sshLineage.parentWorktreeInstanceId,
|
||||
origin: sshLineage.origin,
|
||||
capture: sshLineage.capture,
|
||||
taskId: sshLineage.taskId,
|
||||
coordinatorHandle: sshLineage.coordinatorHandle
|
||||
})
|
||||
store.setState({
|
||||
worktreesByRepo: {
|
||||
repo1: [localWorktree],
|
||||
repo2: [sshWorktree]
|
||||
},
|
||||
worktreeLineageById: {
|
||||
[localWorktree.id]: localLineage,
|
||||
[sshWorktree.id]: sshLineage
|
||||
},
|
||||
workspaceLineageByChildKey: {
|
||||
[localWorkspace.childWorkspaceKey]: localWorkspace,
|
||||
[sshWorkspace.childWorkspaceKey]: sshWorkspace
|
||||
}
|
||||
} as Partial<AppState>)
|
||||
const payload = {
|
||||
lineage: { [localWorktree.id]: localLineage },
|
||||
workspaceLineage: { [localWorkspace.childWorkspaceKey]: localWorkspace }
|
||||
}
|
||||
mockApi.worktrees.listLineage.mockImplementation(async () => structuredClone(payload))
|
||||
const before = store.getState()
|
||||
|
||||
await store.getState().fetchWorktreeLineage()
|
||||
|
||||
expect(store.getState().worktreeLineageById).toBe(before.worktreeLineageById)
|
||||
expect(store.getState().workspaceLineageByChildKey).toBe(before.workspaceLineageByChildKey)
|
||||
expect(store.getState().worktreeLineageById[localWorktree.id]).toBe(localLineage)
|
||||
expect(store.getState().worktreeLineageById[sshWorktree.id]).toBe(sshLineage)
|
||||
expect(store.getState().workspaceLineageByChildKey[localWorkspace.childWorkspaceKey]).toBe(
|
||||
localWorkspace
|
||||
)
|
||||
expect(store.getState().workspaceLineageByChildKey[sshWorkspace.childWorkspaceKey]).toBe(
|
||||
sshWorkspace
|
||||
)
|
||||
})
|
||||
|
||||
it('clears workspace lineage on successful old-shape lineage refresh', async () => {
|
||||
const store = createTestStore()
|
||||
const lineage = makeLineage()
|
||||
|
||||
@@ -133,6 +133,7 @@ import type {
|
||||
import type { DirectSshAuthority } from '../../../../shared/ssh-types'
|
||||
import { findIndexedWorktreeOwnerForHost } from '@/lib/worktree-runtime-owner-index'
|
||||
import { catalogRowsEqual, reuseEqualCatalogRows } from './worktree-catalog-reconciliation'
|
||||
import { reuseEqualRecordMap } from './repo-identity-reconcile'
|
||||
export type { WorktreeSlice, WorktreeDeleteState } from './worktree-helpers'
|
||||
|
||||
// Why: old runtime servers only have `worktree.list`; preserve the large-list UI hydration parity used before `worktree.detectedList` existed.
|
||||
@@ -1570,21 +1571,38 @@ function applyWorktreeLineageUpdate(
|
||||
})
|
||||
}
|
||||
|
||||
function applyHostLineageRefresh(
|
||||
set: Parameters<StateCreator<AppState>>[0],
|
||||
hostId: ExecutionHostId,
|
||||
lineage: {
|
||||
worktreeLineageById: Record<string, WorktreeLineage>
|
||||
workspaceLineageByChildKey: Record<string, WorkspaceLineage>
|
||||
}
|
||||
): void {
|
||||
set((s) => {
|
||||
const worktreeLineageById = mergeLineageForHost(s, hostId, lineage.worktreeLineageById)
|
||||
const workspaceLineageByChildKey = mergeWorkspaceLineageForHost(
|
||||
s,
|
||||
hostId,
|
||||
lineage.workspaceLineageByChildKey
|
||||
)
|
||||
if (
|
||||
worktreeLineageById === s.worktreeLineageById &&
|
||||
workspaceLineageByChildKey === s.workspaceLineageByChildKey
|
||||
) {
|
||||
return s
|
||||
}
|
||||
return { worktreeLineageById, workspaceLineageByChildKey }
|
||||
})
|
||||
}
|
||||
|
||||
async function refreshWorktreeLineageForSettings(
|
||||
settings: AppState['settings'],
|
||||
set: Parameters<StateCreator<AppState>>[0],
|
||||
options: BackgroundRuntimeRefreshOptions = {}
|
||||
): Promise<void> {
|
||||
const lineage = await listWorktreeLineageForRuntime(settings, options)
|
||||
const hostId = getSettingsFocusedExecutionHostId(settings)
|
||||
set((s) => ({
|
||||
worktreeLineageById: mergeLineageForHost(s, hostId, lineage.worktreeLineageById),
|
||||
workspaceLineageByChildKey: mergeWorkspaceLineageForHost(
|
||||
s,
|
||||
hostId,
|
||||
lineage.workspaceLineageByChildKey
|
||||
)
|
||||
}))
|
||||
applyHostLineageRefresh(set, getSettingsFocusedExecutionHostId(settings), lineage)
|
||||
}
|
||||
|
||||
async function refreshRemoteWorktreeLineageBestEffort(
|
||||
@@ -1598,15 +1616,7 @@ async function refreshRemoteWorktreeLineageBestEffort(
|
||||
const lineage = await listWorktreeLineageForRuntime(settings, {
|
||||
reuseRecentCompatibilityFailure: true
|
||||
})
|
||||
const hostId = getSettingsFocusedExecutionHostId(settings)
|
||||
set((s) => ({
|
||||
worktreeLineageById: mergeLineageForHost(s, hostId, lineage.worktreeLineageById),
|
||||
workspaceLineageByChildKey: mergeWorkspaceLineageForHost(
|
||||
s,
|
||||
hostId,
|
||||
lineage.workspaceLineageByChildKey
|
||||
)
|
||||
}))
|
||||
applyHostLineageRefresh(set, getSettingsFocusedExecutionHostId(settings), lineage)
|
||||
} catch (err) {
|
||||
// Why: lineage is supplemental, so a remote timeout here must not discard a successful worktree refresh.
|
||||
console.error('Failed to fetch worktree lineage:', err)
|
||||
@@ -1646,7 +1656,10 @@ function mergeLineageForHost(
|
||||
next[worktreeId] = existing
|
||||
}
|
||||
}
|
||||
return { ...next, ...lineage }
|
||||
for (const [worktreeId, incoming] of Object.entries(lineage)) {
|
||||
next[worktreeId] = incoming
|
||||
}
|
||||
return reuseEqualRecordMap(state.worktreeLineageById, next)
|
||||
}
|
||||
|
||||
function mergeWorkspaceLineageForHost(
|
||||
@@ -1671,7 +1684,10 @@ function mergeWorkspaceLineageForHost(
|
||||
next[childKey] = existing
|
||||
}
|
||||
}
|
||||
return { ...next, ...lineage }
|
||||
for (const [childKey, incoming] of Object.entries(lineage)) {
|
||||
next[childKey] = incoming
|
||||
}
|
||||
return reuseEqualRecordMap(state.workspaceLineageByChildKey, next)
|
||||
}
|
||||
|
||||
async function persistWorktreeMeta(
|
||||
|
||||
Reference in New Issue
Block a user