mirror of
https://github.com/stablyai/orca.git
synced 2026-09-22 08:02:28 +00:00
Reveal workspaces by adjusting only blocking filters
This commit is contained in:
@@ -104,7 +104,8 @@ const WorktreeList = React.memo(function WorktreeList({
|
||||
)
|
||||
|
||||
const agentSendTargetWorktreeId = useAgentSendTargetWorktreeId()
|
||||
const { filterState, hasFilters, clearFilters } = useSidebarWorktreeFilters()
|
||||
const { filterState, hasFilters, clearFilters, revealWorkspaceFilters } =
|
||||
useSidebarWorktreeFilters()
|
||||
const sortedIds = useSidebarWorktreeSortOrder({ allWorktrees, repoMap, sortBy })
|
||||
const manualOrderCatalog = useMemo(
|
||||
() => buildWorktreeManualOrderCatalog({ worktrees: allWorktrees, folderWorkspaces }),
|
||||
@@ -244,7 +245,8 @@ const WorktreeList = React.memo(function WorktreeList({
|
||||
worktrees: allWorktrees,
|
||||
folderWorkspaces,
|
||||
hasFilters,
|
||||
clearFilters
|
||||
clearFilters,
|
||||
revealWorkspaceFilters
|
||||
})
|
||||
|
||||
const filtersHideAllRows = shouldFiltersHideAllRows({
|
||||
|
||||
@@ -1,7 +1,30 @@
|
||||
import { useCallback, useMemo } from 'react'
|
||||
import { useAppStore } from '@/store'
|
||||
import { DEFAULT_SHOW_SLEEPING_WORKSPACES } from '../../../../../../shared/constants'
|
||||
import { computeClearFilterActions, sidebarHasActiveFilters } from '../../visible-worktrees'
|
||||
import {
|
||||
computeClearFilterActions,
|
||||
sidebarHasActiveFilters,
|
||||
isAutomationGeneratedWorkspace,
|
||||
isCliCreatedWorkspace,
|
||||
isDetachedHeadWorkspace,
|
||||
isSleepingSweepExemptWorkspace
|
||||
} from '../../visible-worktrees'
|
||||
import type { Worktree } from '../../../../../../shared/worktree/types'
|
||||
import {
|
||||
getWorktreeExecutionHostId,
|
||||
getSettingsFocusedExecutionHostId
|
||||
} from '../../../../../../shared/execution-host'
|
||||
import { isDefaultBranchWorkspace } from '../../default-branch-workspace'
|
||||
import {
|
||||
getPairedDeviceIdsByEnvironment,
|
||||
isWorkspaceFromOtherDevice
|
||||
} from '../../workspace-creator-visibility'
|
||||
import { getAgentStatusEpochNow } from '@/lib/agent-status-epoch-clock'
|
||||
import { getWorktreeIdsWithLiveAgent, isInactiveWorkspace } from '@/lib/worktree-activity-state'
|
||||
import {
|
||||
getVisibleWorktreeBrowserActivityTabs,
|
||||
getVisibleWorktreeTerminalActivityTabs
|
||||
} from '../../visible-worktree-activity-inputs'
|
||||
|
||||
export type SidebarWorktreeFilters = ReturnType<typeof useSidebarWorktreeFilters>
|
||||
|
||||
@@ -32,6 +55,70 @@ export function useSidebarWorktreeFilters() {
|
||||
const setFilterRepoIds = useAppStore((s) => s.setFilterRepoIds)
|
||||
const setVisibleWorkspaceHostIds = useAppStore((s) => s.setVisibleWorkspaceHostIds)
|
||||
|
||||
const revealWorkspaceFilters = useCallback((worktree: Worktree) => {
|
||||
const state = useAppStore.getState()
|
||||
const repo = state.repos.find((candidate) => candidate.id === worktree.repoId)
|
||||
const targetHostId = getWorktreeExecutionHostId(
|
||||
worktree,
|
||||
repo,
|
||||
getSettingsFocusedExecutionHostId(state.settings)
|
||||
)
|
||||
|
||||
if (state.filterRepoIds.length > 0 && !state.filterRepoIds.includes(worktree.repoId)) {
|
||||
state.setFilterRepoIds([...state.filterRepoIds, worktree.repoId])
|
||||
}
|
||||
const visibleHostIds = state.visibleWorkspaceHostIds
|
||||
const scopedHostIds =
|
||||
visibleHostIds ?? (state.workspaceHostScope === 'all' ? null : [state.workspaceHostScope])
|
||||
if (scopedHostIds && !scopedHostIds.includes(targetHostId)) {
|
||||
state.setVisibleWorkspaceHostIds([...scopedHostIds, targetHostId])
|
||||
}
|
||||
if (state.hideDefaultBranchWorkspace && isDefaultBranchWorkspace(worktree)) {
|
||||
state.setHideDefaultBranchWorkspace(false)
|
||||
}
|
||||
if (state.hideAutomationGeneratedWorkspaces && isAutomationGeneratedWorkspace(worktree)) {
|
||||
state.setHideAutomationGeneratedWorkspaces(false)
|
||||
}
|
||||
if (state.hideCliCreatedWorkspaces && isCliCreatedWorkspace(worktree)) {
|
||||
state.setHideCliCreatedWorkspaces(false)
|
||||
}
|
||||
if (state.hideDetachedHeadWorkspaces && isDetachedHeadWorkspace(worktree)) {
|
||||
state.setHideDetachedHeadWorkspaces(false)
|
||||
}
|
||||
if (state.hideWorkspacesFromOtherDevices) {
|
||||
const pairedDeviceIds = getPairedDeviceIdsByEnvironment(
|
||||
state.runtimeEnvironments,
|
||||
state.runtimeStatusByEnvironmentId
|
||||
)
|
||||
if (isWorkspaceFromOtherDevice(worktree, pairedDeviceIds)) {
|
||||
state.setHideWorkspacesFromOtherDevices(false)
|
||||
}
|
||||
}
|
||||
if (!state.showSleepingWorkspaces) {
|
||||
const tabsByWorktree = getVisibleWorktreeTerminalActivityTabs(state.tabsByWorktree)
|
||||
const browserTabsByWorktree = getVisibleWorktreeBrowserActivityTabs(
|
||||
state.browserTabsByWorktree
|
||||
)
|
||||
const liveAgentWorktrees = getWorktreeIdsWithLiveAgent(
|
||||
state.agentStatusByPaneKey,
|
||||
tabsByWorktree,
|
||||
getAgentStatusEpochNow(state.agentStatusEpoch)
|
||||
)
|
||||
if (
|
||||
!isSleepingSweepExemptWorkspace(worktree, state.alwaysShowDefaultBranchWorkspace) &&
|
||||
isInactiveWorkspace(
|
||||
worktree.id,
|
||||
tabsByWorktree,
|
||||
state.ptyIdsByTabId,
|
||||
browserTabsByWorktree,
|
||||
liveAgentWorktrees
|
||||
)
|
||||
) {
|
||||
state.setShowSleepingWorkspaces(true)
|
||||
}
|
||||
}
|
||||
}, [])
|
||||
|
||||
// Why: count hideDefaultBranchWorkspace as a filter so the Clear Filters escape hatch stays reachable when it alone empties the list.
|
||||
const filterState = useMemo(
|
||||
() => ({
|
||||
@@ -102,5 +189,10 @@ export function useSidebarWorktreeFilters() {
|
||||
filterState
|
||||
])
|
||||
|
||||
return { filterState, hasFilters: sidebarHasActiveFilters(filterState), clearFilters }
|
||||
return {
|
||||
filterState,
|
||||
hasFilters: sidebarHasActiveFilters(filterState),
|
||||
clearFilters,
|
||||
revealWorkspaceFilters
|
||||
}
|
||||
}
|
||||
|
||||
+17
-5
@@ -103,7 +103,9 @@ describe('revealing a filtered workspace', () => {
|
||||
it('explains the filter reset and leaves filters intact when dismissed', async () => {
|
||||
await render()
|
||||
await act(async () => requestScrollToCurrentWorkspaceReveal())
|
||||
expect(document.body.textContent).toContain('Revealing it will clear your sidebar filters.')
|
||||
expect(document.body.textContent).toContain(
|
||||
'Revealing it will adjust only the filters hiding it.'
|
||||
)
|
||||
expect(args.clearFilters).not.toHaveBeenCalled()
|
||||
expect(state.revealWorktreeInSidebar).not.toHaveBeenCalled()
|
||||
await click('Keep filters')
|
||||
@@ -111,13 +113,23 @@ describe('revealing a filtered workspace', () => {
|
||||
expect(state.revealWorktreeInSidebar).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('delegates to the minimal filter revealer when provided', async () => {
|
||||
const revealWorkspaceFilters = vi.fn()
|
||||
args = { ...args, revealWorkspaceFilters }
|
||||
await render()
|
||||
await act(async () => requestScrollToCurrentWorkspaceReveal())
|
||||
await click('Adjust filters and reveal')
|
||||
expect(revealWorkspaceFilters).toHaveBeenCalledWith(args.worktrees[0])
|
||||
expect(args.clearFilters).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('clears filters and reveals on the original execution host only after confirmation', async () => {
|
||||
await render()
|
||||
await act(async () => {
|
||||
requestScrollToCurrentWorkspaceReveal()
|
||||
requestScrollToCurrentWorkspaceReveal()
|
||||
})
|
||||
await click('Clear filters and reveal')
|
||||
await click('Adjust filters and reveal')
|
||||
expect(args.clearFilters).toHaveBeenCalledTimes(1)
|
||||
expect(state.revealWorktreeInSidebar).toHaveBeenCalledWith('wt-1', {
|
||||
behavior: 'smooth',
|
||||
@@ -174,7 +186,7 @@ describe('revealing a filtered workspace', () => {
|
||||
await act(async () => requestScrollToCurrentWorkspaceReveal())
|
||||
args = { ...args, visibleWorktrees: args.worktrees }
|
||||
await render()
|
||||
await click('Clear filters and reveal')
|
||||
await click('Adjust filters and reveal')
|
||||
expect(args.clearFilters).not.toHaveBeenCalled()
|
||||
expect(state.revealWorktreeInSidebar).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
@@ -184,7 +196,7 @@ describe('revealing a filtered workspace', () => {
|
||||
await act(async () => requestScrollToCurrentWorkspaceReveal())
|
||||
args = { ...args, currentSidebarWorktreeId: 'wt-2' }
|
||||
await render()
|
||||
await click('Clear filters and reveal')
|
||||
await click('Adjust filters and reveal')
|
||||
expect(args.clearFilters).not.toHaveBeenCalled()
|
||||
expect(state.revealWorktreeInSidebar).not.toHaveBeenCalled()
|
||||
})
|
||||
@@ -219,7 +231,7 @@ describe('revealing a filtered workspace', () => {
|
||||
await act(async () => requestScrollToCurrentWorkspaceRevealAndRename())
|
||||
expect(args.clearFilters).not.toHaveBeenCalled()
|
||||
if (filtered) {
|
||||
await click('Clear filters and reveal')
|
||||
await click('Adjust filters and reveal')
|
||||
expect(args.clearFilters).toHaveBeenCalledTimes(1)
|
||||
} else {
|
||||
expect(document.querySelector('[role="dialog"]')).toBeNull()
|
||||
|
||||
+17
-7
@@ -41,6 +41,7 @@ export function useSidebarRevealRequests(args: {
|
||||
folderWorkspaces: readonly FolderWorkspace[]
|
||||
hasFilters: boolean
|
||||
clearFilters: () => void
|
||||
revealWorkspaceFilters?: (worktree: Worktree) => void
|
||||
}): void {
|
||||
const {
|
||||
groupBy,
|
||||
@@ -53,7 +54,8 @@ export function useSidebarRevealRequests(args: {
|
||||
worktrees,
|
||||
folderWorkspaces,
|
||||
hasFilters,
|
||||
clearFilters
|
||||
clearFilters,
|
||||
revealWorkspaceFilters = clearFilters
|
||||
} = args
|
||||
const setGroupBy = useAppStore((s) => s.setGroupBy)
|
||||
const pendingRevealSidebarRow = useAppStore((s) => s.pendingRevealSidebarRow)
|
||||
@@ -80,7 +82,12 @@ export function useSidebarRevealRequests(args: {
|
||||
return
|
||||
}
|
||||
if (!renderedSidebarRowKeys.has(rowKey) && hasFilters) {
|
||||
clearFilters()
|
||||
const target = worktreeMap.get(rowKey)
|
||||
if (target) {
|
||||
revealWorkspaceFilters(target)
|
||||
} else {
|
||||
clearFilters()
|
||||
}
|
||||
}
|
||||
}, [
|
||||
clearFilters,
|
||||
@@ -88,7 +95,9 @@ export function useSidebarRevealRequests(args: {
|
||||
hasFilters,
|
||||
pendingRevealSidebarRow,
|
||||
renderedSidebarRowKeys,
|
||||
setGroupBy
|
||||
setGroupBy,
|
||||
worktreeMap,
|
||||
revealWorkspaceFilters
|
||||
])
|
||||
|
||||
const handleRevealCurrentWorkspaceRequest = useCallback(
|
||||
@@ -139,9 +148,9 @@ export function useSidebarRevealRequests(args: {
|
||||
title: translate('sidebar.revealFiltered.title', 'Reveal hidden workspace?'),
|
||||
description: translate(
|
||||
'sidebar.revealFiltered.description',
|
||||
'The active workspace is hidden in the sidebar. Revealing it will clear your sidebar filters.'
|
||||
'The active workspace is hidden in the sidebar. Revealing it will adjust only the filters hiding it.'
|
||||
),
|
||||
confirmLabel: translate('sidebar.revealFiltered.confirm', 'Clear filters and reveal'),
|
||||
confirmLabel: translate('sidebar.revealFiltered.confirm', 'Adjust filters and reveal'),
|
||||
cancelLabel: translate('sidebar.revealFiltered.cancel', 'Keep filters')
|
||||
})
|
||||
} finally {
|
||||
@@ -164,7 +173,7 @@ export function useSidebarRevealRequests(args: {
|
||||
latest.visibleFolderWorkspaces
|
||||
)
|
||||
) {
|
||||
latest.clearFilters()
|
||||
revealWorkspaceFilters(activeWorktree)
|
||||
}
|
||||
}
|
||||
revealWorktreeInSidebar(currentSidebarWorktreeId, {
|
||||
@@ -185,7 +194,8 @@ export function useSidebarRevealRequests(args: {
|
||||
visibleFolderWorkspaces,
|
||||
revealWorktreeInSidebar,
|
||||
worktreeMap,
|
||||
worktrees
|
||||
worktrees,
|
||||
revealWorkspaceFilters
|
||||
]
|
||||
)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user