mirror of
https://github.com/stablyai/orca.git
synced 2026-09-29 08:03:20 +00:00
fix(sidebar): reveal collapsed workspaces without clearing filters
This commit is contained in:
@@ -236,7 +236,8 @@ const WorktreeList = React.memo(function WorktreeList({
|
||||
useSidebarRevealRequests({
|
||||
groupBy,
|
||||
renderedSidebarRowKeys: rowModel.renderedSidebarRowKeys,
|
||||
renderedWorktreeIdentities: selection.renderedWorktreeIdentities,
|
||||
visibleWorktrees,
|
||||
visibleFolderWorkspaces: visibleScope.visibleFolderWorkspacesForRows,
|
||||
currentSidebarWorktreeId,
|
||||
currentSidebarExecutionHostId: activeWorkspaceExecutionHostId,
|
||||
worktreeMap,
|
||||
|
||||
+80
-36
@@ -82,7 +82,8 @@ beforeEach(() => {
|
||||
args = {
|
||||
groupBy: 'repo',
|
||||
renderedSidebarRowKeys: new Set(),
|
||||
renderedWorktreeIdentities: [],
|
||||
visibleWorktrees: [],
|
||||
visibleFolderWorkspaces: [],
|
||||
currentSidebarWorktreeId: worktree.id,
|
||||
currentSidebarExecutionHostId: 'ssh:dev',
|
||||
worktreeMap: new Map([[worktree.id, worktree]]),
|
||||
@@ -133,7 +134,7 @@ describe('revealing a filtered workspace', () => {
|
||||
args = {
|
||||
...args,
|
||||
hasFilters: visible,
|
||||
renderedWorktreeIdentities: visible ? ['ssh:dev|wt-1'] : []
|
||||
visibleWorktrees: visible ? args.worktrees : []
|
||||
}
|
||||
await render()
|
||||
await act(async () => requestScrollToCurrentWorkspaceReveal())
|
||||
@@ -143,6 +144,41 @@ describe('revealing a filtered workspace', () => {
|
||||
}
|
||||
)
|
||||
|
||||
it.each(['ssh:dev', null] as const)(
|
||||
'reveals a collapsed workspace that passes filters with active host %s',
|
||||
async (executionHostId) => {
|
||||
args = {
|
||||
...args,
|
||||
currentSidebarExecutionHostId: executionHostId,
|
||||
visibleWorktrees: args.worktrees
|
||||
}
|
||||
await render()
|
||||
await act(async () => requestScrollToCurrentWorkspaceReveal())
|
||||
expect(document.querySelector('[role="dialog"]')).toBeNull()
|
||||
expect(args.clearFilters).not.toHaveBeenCalled()
|
||||
expect(state.revealWorktreeInSidebar).toHaveBeenCalledTimes(1)
|
||||
}
|
||||
)
|
||||
|
||||
it('does not let a visible same-id workspace on another host bypass confirmation', async () => {
|
||||
args = { ...args, visibleWorktrees: [{ ...args.worktrees[0], hostId: 'local' }] }
|
||||
await render()
|
||||
await act(async () => requestScrollToCurrentWorkspaceReveal())
|
||||
expect(document.querySelector('[role="dialog"]')).not.toBeNull()
|
||||
expect(state.revealWorktreeInSidebar).not.toHaveBeenCalled()
|
||||
await click('Keep filters')
|
||||
})
|
||||
|
||||
it('preserves filters when the target becomes included while confirmation is open', async () => {
|
||||
await render()
|
||||
await act(async () => requestScrollToCurrentWorkspaceReveal())
|
||||
args = { ...args, visibleWorktrees: args.worktrees }
|
||||
await render()
|
||||
await click('Clear filters and reveal')
|
||||
expect(args.clearFilters).not.toHaveBeenCalled()
|
||||
expect(state.revealWorktreeInSidebar).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
|
||||
it('does not apply a stale confirmation after switching workspaces', async () => {
|
||||
await render()
|
||||
await act(async () => requestScrollToCurrentWorkspaceReveal())
|
||||
@@ -153,39 +189,47 @@ describe('revealing a filtered workspace', () => {
|
||||
expect(state.revealWorktreeInSidebar).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('confirms filtered folder workspaces and preserves the rename request', async () => {
|
||||
args = {
|
||||
...args,
|
||||
currentSidebarWorktreeId: folderWorkspaceKey('folder-1'),
|
||||
currentSidebarExecutionHostId: null,
|
||||
folderWorkspaces: [
|
||||
{
|
||||
id: 'folder-1',
|
||||
projectGroupId: 'project-1',
|
||||
name: 'Notes',
|
||||
folderPath: '/notes',
|
||||
linkedTask: null,
|
||||
comment: '',
|
||||
isArchived: false,
|
||||
isUnread: false,
|
||||
isPinned: false,
|
||||
sortOrder: 1,
|
||||
lastActivityAt: 1,
|
||||
createdAt: 1,
|
||||
updatedAt: 1
|
||||
}
|
||||
]
|
||||
it.each([true, false])(
|
||||
'reveals folder workspaces and preserves rename (filtered: %s)',
|
||||
async (filtered) => {
|
||||
args = {
|
||||
...args,
|
||||
currentSidebarWorktreeId: folderWorkspaceKey('folder-1'),
|
||||
currentSidebarExecutionHostId: null,
|
||||
folderWorkspaces: [
|
||||
{
|
||||
id: 'folder-1',
|
||||
projectGroupId: 'project-1',
|
||||
name: 'Notes',
|
||||
folderPath: '/notes',
|
||||
linkedTask: null,
|
||||
comment: '',
|
||||
isArchived: false,
|
||||
isUnread: false,
|
||||
isPinned: false,
|
||||
sortOrder: 1,
|
||||
lastActivityAt: 1,
|
||||
createdAt: 1,
|
||||
updatedAt: 1
|
||||
}
|
||||
]
|
||||
}
|
||||
args.visibleFolderWorkspaces = filtered ? [] : args.folderWorkspaces
|
||||
await render()
|
||||
await act(async () => requestScrollToCurrentWorkspaceRevealAndRename())
|
||||
expect(args.clearFilters).not.toHaveBeenCalled()
|
||||
if (filtered) {
|
||||
await click('Clear filters and reveal')
|
||||
expect(args.clearFilters).toHaveBeenCalledTimes(1)
|
||||
} else {
|
||||
expect(document.querySelector('[role="dialog"]')).toBeNull()
|
||||
}
|
||||
expect(state.revealWorktreeInSidebar).toHaveBeenCalledWith(folderWorkspaceKey('folder-1'), {
|
||||
behavior: 'smooth',
|
||||
highlight: true,
|
||||
beginRename: true,
|
||||
executionHostId: undefined
|
||||
})
|
||||
}
|
||||
await render()
|
||||
await act(async () => requestScrollToCurrentWorkspaceRevealAndRename())
|
||||
expect(args.clearFilters).not.toHaveBeenCalled()
|
||||
await click('Clear filters and reveal')
|
||||
expect(args.clearFilters).toHaveBeenCalledTimes(1)
|
||||
expect(state.revealWorktreeInSidebar).toHaveBeenCalledWith(folderWorkspaceKey('folder-1'), {
|
||||
behavior: 'smooth',
|
||||
highlight: true,
|
||||
beginRename: true,
|
||||
executionHostId: undefined
|
||||
})
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
+33
-10
@@ -10,16 +10,30 @@ import {
|
||||
import type { FolderWorkspace } from '../../../../../../shared/folder-workspace-types'
|
||||
import type { Worktree } from '../../../../../../shared/worktree/types'
|
||||
import type { ExecutionHostId } from '../../../../../../shared/execution-host'
|
||||
import { composeWorktreeHostIdentity } from '../../../../../../shared/worktree/host-qualified-identity'
|
||||
import { getWorktreeHostIdentity } from '../../../../../../shared/worktree/host-qualified-identity'
|
||||
import { folderWorkspaceKey } from '../../../../../../shared/workspace-scope'
|
||||
import type { WorktreeGroupBy } from '../grouping/row-types'
|
||||
import { getKnownSidebarWorktreeById } from './folder-reveal'
|
||||
|
||||
function workspacePassesFilters(
|
||||
worktree: Worktree,
|
||||
worktrees: readonly Worktree[],
|
||||
folderWorkspaces: readonly FolderWorkspace[]
|
||||
): boolean {
|
||||
const identity = getWorktreeHostIdentity(worktree)
|
||||
return (
|
||||
worktrees.some((candidate) => getWorktreeHostIdentity(candidate) === identity) ||
|
||||
folderWorkspaces.some((workspace) => folderWorkspaceKey(workspace.id) === worktree.id)
|
||||
)
|
||||
}
|
||||
|
||||
// Turns a "show me the current workspace" request into whatever the sidebar must change
|
||||
// first — grouping mode, active filters — before the viewport can scroll to it.
|
||||
export function useSidebarRevealRequests(args: {
|
||||
groupBy: WorktreeGroupBy
|
||||
renderedSidebarRowKeys: ReadonlySet<string>
|
||||
renderedWorktreeIdentities: readonly string[]
|
||||
visibleWorktrees: readonly Worktree[]
|
||||
visibleFolderWorkspaces: readonly FolderWorkspace[]
|
||||
currentSidebarWorktreeId: string | null
|
||||
currentSidebarExecutionHostId: ExecutionHostId | null
|
||||
worktreeMap: Map<string, Worktree>
|
||||
@@ -31,7 +45,8 @@ export function useSidebarRevealRequests(args: {
|
||||
const {
|
||||
groupBy,
|
||||
renderedSidebarRowKeys,
|
||||
renderedWorktreeIdentities,
|
||||
visibleWorktrees,
|
||||
visibleFolderWorkspaces,
|
||||
currentSidebarWorktreeId,
|
||||
currentSidebarExecutionHostId,
|
||||
worktreeMap,
|
||||
@@ -106,11 +121,11 @@ export function useSidebarRevealRequests(args: {
|
||||
if (!activeWorktree || activeWorktree.isArchived) {
|
||||
return
|
||||
}
|
||||
const currentIdentity = composeWorktreeHostIdentity(
|
||||
currentSidebarExecutionHostId ?? undefined,
|
||||
currentSidebarWorktreeId
|
||||
)
|
||||
if (hasFilters && !renderedWorktreeIdentities.includes(currentIdentity)) {
|
||||
// Collapsed groups hide rows without excluding their workspaces from the filter results.
|
||||
if (
|
||||
hasFilters &&
|
||||
!workspacePassesFilters(activeWorktree, visibleWorktrees, visibleFolderWorkspaces)
|
||||
) {
|
||||
if (confirmationPending.current) {
|
||||
return
|
||||
}
|
||||
@@ -141,7 +156,14 @@ export function useSidebarRevealRequests(args: {
|
||||
) {
|
||||
return
|
||||
}
|
||||
if (latest.hasFilters && !latest.renderedWorktreeIdentities.includes(currentIdentity)) {
|
||||
if (
|
||||
latest.hasFilters &&
|
||||
!workspacePassesFilters(
|
||||
activeWorktree,
|
||||
latest.visibleWorktrees,
|
||||
latest.visibleFolderWorkspaces
|
||||
)
|
||||
) {
|
||||
latest.clearFilters()
|
||||
}
|
||||
}
|
||||
@@ -159,7 +181,8 @@ export function useSidebarRevealRequests(args: {
|
||||
currentSidebarExecutionHostId,
|
||||
folderWorkspaces,
|
||||
revealSidebarRow,
|
||||
renderedWorktreeIdentities,
|
||||
visibleWorktrees,
|
||||
visibleFolderWorkspaces,
|
||||
revealWorktreeInSidebar,
|
||||
worktreeMap,
|
||||
worktrees
|
||||
|
||||
Reference in New Issue
Block a user