mirror of
https://github.com/stablyai/orca.git
synced 2026-10-31 00:57:45 +00:00
fix(workspace): keep the active workspace key in sync with the active worktree (#14475)
setActiveWorktree's no-op guard now compares activeWorkspaceKey with the key derived from the target worktree, so reselecting the current worktree repairs a stale key instead of returning early. Session hydration always derives activeWorkspaceKey from the restored activeWorktreeId instead of trusting a persisted key that may name another workspace, which kept the wrong sidebar row highlighted after restart. Fixes #14474 Co-authored-by: Neil <neil@stably.ai>
This commit is contained in:
@@ -2,6 +2,7 @@ import { describe, it, expect, vi, beforeEach } from 'vitest'
|
||||
import { installAttentionScreen } from './workspace-attention/workspace-unread-characterization-fixture'
|
||||
import type * as AgentStatusModule from '@/lib/agent-status'
|
||||
import { buildWorktreeComparator } from '../../../../shared/sidebar-list/smart-sort'
|
||||
import { worktreeWorkspaceKey } from '../../../../shared/workspace-scope'
|
||||
import {
|
||||
createTestStore,
|
||||
makeOpenFile,
|
||||
@@ -230,6 +231,7 @@ describe('setActiveWorktree', () => {
|
||||
repo1: [makeWorktree({ id: wt, repoId: 'repo1', path: '/path/wt1' })]
|
||||
},
|
||||
activeWorktreeId: wt,
|
||||
activeWorkspaceKey: worktreeWorkspaceKey(wt),
|
||||
activeTabId: tabId,
|
||||
activeTabType: 'terminal',
|
||||
activeTabTypeByWorktree: { [wt]: 'terminal' },
|
||||
|
||||
@@ -629,6 +629,29 @@ describe('hydrateWorkspaceSession', () => {
|
||||
expect(store.getState().worktreeNavHistory).toEqual([worktreeId])
|
||||
})
|
||||
|
||||
it('ignores a persisted workspace key that disagrees with the restored active worktree', () => {
|
||||
const store = createTestStore()
|
||||
const worktreeId = 'repo1::/wt-current'
|
||||
const staleFolderKey = folderWorkspaceKey('folder-stale')
|
||||
seedStore(store, {
|
||||
worktreesByRepo: {
|
||||
repo1: [makeWorktree({ id: worktreeId, repoId: 'repo1', path: '/wt-current' })]
|
||||
}
|
||||
})
|
||||
|
||||
store.getState().hydrateWorkspaceSession(
|
||||
{
|
||||
...getDefaultWorkspaceSession(),
|
||||
activeRepoId: 'repo1',
|
||||
activeWorktreeId: worktreeId,
|
||||
activeWorkspaceKey: staleFolderKey
|
||||
},
|
||||
{ additionalValidWorkspaceKeys: [staleFolderKey] }
|
||||
)
|
||||
|
||||
expect(store.getState().activeWorkspaceKey).toBe(worktreeWorkspaceKey(worktreeId))
|
||||
})
|
||||
|
||||
it('leaves nav history empty when no active worktree is restored', () => {
|
||||
const store = createTestStore()
|
||||
seedStore(store, { worktreesByRepo: {} })
|
||||
|
||||
@@ -112,6 +112,24 @@ describe('setActiveWorktree focus handling', () => {
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
it('repairs a stale workspace key when reactivating the current worktree', () => {
|
||||
const store = createTestStore()
|
||||
const worktree = makeWorktree({ id: 'repo1::/path/current', repoId: 'repo1' })
|
||||
store.setState({
|
||||
worktreesByRepo: { repo1: [worktree] },
|
||||
activeWorktreeId: worktree.id,
|
||||
activeWorkspaceKey: worktreeWorkspaceKey('repo1::/path/stale'),
|
||||
activeWorkspaceExecutionHostId: null,
|
||||
activeTabTypeByWorktree: { [worktree.id]: 'terminal' },
|
||||
rightSidebarExplorerView: 'files',
|
||||
everActivatedWorktreeIds: new Set([worktree.id])
|
||||
})
|
||||
|
||||
store.getState().setActiveWorktree(worktree.id)
|
||||
|
||||
expect(store.getState().activeWorkspaceKey).toBe(worktreeWorkspaceKey(worktree.id))
|
||||
})
|
||||
})
|
||||
|
||||
describe('markWorktreeVisited', () => {
|
||||
|
||||
@@ -76,6 +76,9 @@ export function createSetActiveWorktree(
|
||||
}
|
||||
}
|
||||
|
||||
const nextActiveWorkspaceKey = isWorkspaceKey(worktreeId)
|
||||
? worktreeId
|
||||
: worktreeWorkspaceKey(worktreeId)
|
||||
const worktree = findKnownWorktreeById(s, worktreeId, executionHostId)
|
||||
const {
|
||||
restoredRightSidebarExplorerView,
|
||||
@@ -124,6 +127,7 @@ export function createSetActiveWorktree(
|
||||
: { ...s.activeTabTypeByWorktree, [worktreeId]: activeTabType }
|
||||
const hasStateChange =
|
||||
s.activeWorktreeId !== worktreeId ||
|
||||
s.activeWorkspaceKey !== nextActiveWorkspaceKey ||
|
||||
s.activeWorkspaceExecutionHostId !== (executionHostId ?? null) ||
|
||||
// Why: a pending-creation panel can show over the prior worktree; a non-null activePendingCreationId counts as a change.
|
||||
s.activePendingCreationId !== null ||
|
||||
@@ -148,9 +152,7 @@ export function createSetActiveWorktree(
|
||||
...reconciliation?.patch,
|
||||
activeRepoId: nextActiveRepoId,
|
||||
activeWorktreeId: worktreeId,
|
||||
activeWorkspaceKey: isWorkspaceKey(worktreeId)
|
||||
? worktreeId
|
||||
: worktreeWorkspaceKey(worktreeId),
|
||||
activeWorkspaceKey: nextActiveWorkspaceKey,
|
||||
activeWorkspaceExecutionHostId: executionHostId ?? null,
|
||||
activePendingCreationId: null,
|
||||
activeFileId,
|
||||
|
||||
@@ -2,7 +2,7 @@ import type { WorkspaceKey } from '../../../../shared/folder-workspace-types'
|
||||
import { FLOATING_TERMINAL_WORKTREE_ID } from '../../../../shared/constants'
|
||||
import {
|
||||
folderWorkspaceKey,
|
||||
parseWorkspaceKey,
|
||||
isWorkspaceKey,
|
||||
worktreeWorkspaceKey
|
||||
} from '../../../../shared/workspace-scope'
|
||||
import { buildByIdIndex } from '../slices/worktree-by-id-index'
|
||||
@@ -87,14 +87,12 @@ export function createWorkspaceTerminalHydrationActions(
|
||||
// An empty workspace is valid; fall back from active repo to avoid a blank landing screen.
|
||||
return fallbackActiveWorktreeId
|
||||
})()
|
||||
const activeWorkspaceKey: WorkspaceKey | null =
|
||||
session.activeWorkspaceKey && validWorktreeIds.has(session.activeWorkspaceKey)
|
||||
? session.activeWorkspaceKey
|
||||
: activeWorktreeId
|
||||
? parseWorkspaceKey(activeWorktreeId)
|
||||
? (activeWorktreeId as WorkspaceKey)
|
||||
: worktreeWorkspaceKey(activeWorktreeId)
|
||||
: null
|
||||
// Derive from the restored worktree; older builds could persist a key naming another workspace.
|
||||
const activeWorkspaceKey: WorkspaceKey | null = activeWorktreeId
|
||||
? isWorkspaceKey(activeWorktreeId)
|
||||
? activeWorktreeId
|
||||
: worktreeWorkspaceKey(activeWorktreeId)
|
||||
: null
|
||||
const activeWorkspaceExecutionHostId =
|
||||
activeWorkspaceKey && session.activeWorkspaceExecutionHostId
|
||||
? session.activeWorkspaceExecutionHostId
|
||||
|
||||
Reference in New Issue
Block a user