mirror of
https://github.com/stablyai/orca.git
synced 2026-09-30 08:03:12 +00:00
feat(palette): seed Cmd+J filter from sidebar show scope
When opening Cmd+J, the palette's host and project filters now initialize from the sidebar's current Show scope, so results match the user's sidebar view. The palette can still be cleared or changed per open; sidebar never reads back palette filters.
This commit is contained in:
@@ -2,6 +2,7 @@ import { describe, expect, it } from 'vitest'
|
||||
import type { ExecutionHostId } from '../../../../shared/execution-host'
|
||||
import {
|
||||
addPaletteFilterValues,
|
||||
buildPaletteFilterFromSidebarScope,
|
||||
buildPaletteFilterPredicate,
|
||||
clearPaletteFilterField,
|
||||
EMPTY_PALETTE_FILTER,
|
||||
@@ -212,3 +213,65 @@ describe('buildPaletteFilterPredicate', () => {
|
||||
expect(withProject?.matchesGroupHostId('ssh:builder')).toBe(false)
|
||||
})
|
||||
})
|
||||
|
||||
describe('buildPaletteFilterFromSidebarScope', () => {
|
||||
const allHosts = { workspaceHostScope: 'all', visibleWorkspaceHostIds: null } as const
|
||||
|
||||
it('opens unfiltered when the sidebar shows every host and project', () => {
|
||||
expect(buildPaletteFilterFromSidebarScope({ ...allHosts, filterRepoIds: [] }, model)).toBe(
|
||||
EMPTY_PALETTE_FILTER
|
||||
)
|
||||
})
|
||||
|
||||
it('seeds the host chips from the sidebar host scope', () => {
|
||||
expect(
|
||||
buildPaletteFilterFromSidebarScope(
|
||||
{ workspaceHostScope: 'ssh:builder', visibleWorkspaceHostIds: null, filterRepoIds: [] },
|
||||
model
|
||||
)
|
||||
).toEqual(filterOf(['ssh:builder'], []))
|
||||
expect(
|
||||
buildPaletteFilterFromSidebarScope(
|
||||
{
|
||||
workspaceHostScope: 'all',
|
||||
visibleWorkspaceHostIds: ['runtime:env-1', 'local'],
|
||||
filterRepoIds: []
|
||||
},
|
||||
model
|
||||
)
|
||||
).toEqual(filterOf(['local', 'runtime:env-1'], []))
|
||||
})
|
||||
|
||||
it('maps sidebar repo picks onto the project rows that contain them', () => {
|
||||
expect(
|
||||
buildPaletteFilterFromSidebarScope({ ...allHosts, filterRepoIds: ['r2'] }, model)
|
||||
).toEqual(filterOf([], ['project:p1']))
|
||||
})
|
||||
|
||||
it('treats a scope that covers every option as no filter', () => {
|
||||
// Every host explicitly listed is the same as "all hosts": no chips to clear.
|
||||
expect(
|
||||
buildPaletteFilterFromSidebarScope(
|
||||
{
|
||||
workspaceHostScope: 'all',
|
||||
visibleWorkspaceHostIds: ['local', 'ssh:builder', 'runtime:env-1'],
|
||||
filterRepoIds: ['r1', 'r3']
|
||||
},
|
||||
model
|
||||
)
|
||||
).toBe(EMPTY_PALETTE_FILTER)
|
||||
})
|
||||
|
||||
it('falls back to a global search when the scope matches nothing the palette can show', () => {
|
||||
expect(
|
||||
buildPaletteFilterFromSidebarScope(
|
||||
{
|
||||
workspaceHostScope: 'ssh:gone',
|
||||
visibleWorkspaceHostIds: null,
|
||||
filterRepoIds: ['r-gone']
|
||||
},
|
||||
model
|
||||
)
|
||||
).toBe(EMPTY_PALETTE_FILTER)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import type { ExecutionHostId } from '../../../../shared/execution-host'
|
||||
import { getVisibleWorkspaceHostIdSet } from '../sidebar/visible-worktree-host-scope'
|
||||
import type { Worktree } from '../../../../shared/worktree/types'
|
||||
import {
|
||||
resolveRepoFilterHostId,
|
||||
@@ -119,6 +120,45 @@ export function reconcilePaletteFilter(
|
||||
return { hostIds, projectKeys }
|
||||
}
|
||||
|
||||
export type SidebarScopeForPaletteFilter = Parameters<typeof getVisibleWorkspaceHostIdSet>[0] & {
|
||||
filterRepoIds: readonly string[]
|
||||
}
|
||||
|
||||
/**
|
||||
* Seeds the palette's host/project selection from the sidebar's "Show" scope so
|
||||
* Cmd+J opens on the same slice the user is looking at. One-way: the user can
|
||||
* clear or change it per open, and the sidebar never reads it back. A field
|
||||
* that would select every option is left empty — that is not a filter, just chips.
|
||||
*/
|
||||
export function buildPaletteFilterFromSidebarScope(
|
||||
scope: SidebarScopeForPaletteFilter,
|
||||
model: PaletteFilterModel
|
||||
): PaletteFilterState {
|
||||
const visibleHostIds = getVisibleWorkspaceHostIdSet(scope)
|
||||
let hostIds: readonly string[] = []
|
||||
if (visibleHostIds) {
|
||||
const available = model.hosts.filter((host) => visibleHostIds.has(host.id as ExecutionHostId))
|
||||
hostIds = available.length < model.hosts.length ? available.map((host) => host.id).sort() : []
|
||||
}
|
||||
|
||||
let projectKeys: readonly string[] = []
|
||||
if (scope.filterRepoIds.length > 0) {
|
||||
const selectedRepoIds = new Set(scope.filterRepoIds)
|
||||
const available = model.projects.filter((project) =>
|
||||
(model.repoIdsByProjectKey.get(project.id) ?? []).some((repoId) =>
|
||||
selectedRepoIds.has(repoId)
|
||||
)
|
||||
)
|
||||
projectKeys =
|
||||
available.length < model.projects.length ? available.map((project) => project.id).sort() : []
|
||||
}
|
||||
|
||||
if (hostIds.length === 0 && projectKeys.length === 0) {
|
||||
return EMPTY_PALETTE_FILTER
|
||||
}
|
||||
return { hostIds, projectKeys }
|
||||
}
|
||||
|
||||
export type PaletteFilterPredicate = {
|
||||
matchesWorktree: (worktree: Pick<Worktree, 'repoId' | 'hostId'>) => boolean
|
||||
/** Keyed on the row, not a repo: one project row can span repos on several hosts. */
|
||||
|
||||
@@ -4,7 +4,7 @@ import {
|
||||
queueBrowserFocusRequest
|
||||
} from '@/components/browser-pane/host-guest/browser-focus'
|
||||
import { captureCmdJActiveGroupSnapshot } from '@/components/cmd-j/quick-action-context'
|
||||
import { EMPTY_PALETTE_FILTER } from '@/components/cmd-j/palette-filter'
|
||||
import { buildPaletteFilterFromSidebarScope } from '@/components/cmd-j/palette-filter'
|
||||
import { resolvePaletteFocusRestoreTarget } from '@/components/cmd-j/palette-focus-restore-target'
|
||||
import {
|
||||
CREATE_WORKTREE_ITEM_ID,
|
||||
@@ -12,6 +12,7 @@ import {
|
||||
} from '@/lib/worktree-palette-create-action'
|
||||
import { useAppStore } from '@/store'
|
||||
import { CREATE_WORKSPACE_QUICK_ACTION_ITEM_ID } from './worktree-jump-palette-model'
|
||||
import type { WorktreeJumpPaletteFilter } from './use-worktree-jump-palette-filter'
|
||||
import type { WorktreeJumpPaletteListEntries } from './use-worktree-jump-palette-list-entries'
|
||||
import type { WorktreeJumpPaletteLocalState } from './use-worktree-jump-palette-local-state'
|
||||
import type { WorktreeJumpPaletteOpenTabs } from './use-worktree-jump-palette-open-tabs'
|
||||
@@ -23,6 +24,7 @@ import type { WorktreeJumpPaletteWorktrees } from './use-worktree-jump-palette-w
|
||||
|
||||
type WorktreeJumpPaletteSelectionLifecycleInput = WorktreeJumpPaletteStoreState &
|
||||
WorktreeJumpPaletteLocalState &
|
||||
WorktreeJumpPaletteFilter &
|
||||
WorktreeJumpPaletteOpenTabs &
|
||||
WorktreeJumpPaletteProjectTargets &
|
||||
WorktreeJumpPaletteQuickActions &
|
||||
@@ -57,6 +59,10 @@ export function useWorktreeJumpPaletteSelectionLifecycle({
|
||||
setQuery,
|
||||
setSelectedItemId,
|
||||
setRawFilter,
|
||||
filterModel,
|
||||
workspaceHostScope,
|
||||
visibleWorkspaceHostIds,
|
||||
filterRepoIds,
|
||||
selectionMovedByUserRef,
|
||||
taskSourceUrl,
|
||||
listRef,
|
||||
@@ -108,7 +114,13 @@ export function useWorktreeJumpPaletteSelectionLifecycle({
|
||||
setQuery('')
|
||||
setSelectedItemId('')
|
||||
selectionMovedByUserRef.current = false
|
||||
setRawFilter(EMPTY_PALETTE_FILTER)
|
||||
// Each open starts from the sidebar's current Show scope, not the last palette selection.
|
||||
setRawFilter(
|
||||
buildPaletteFilterFromSidebarScope(
|
||||
{ workspaceHostScope, visibleWorkspaceHostIds, filterRepoIds },
|
||||
filterModel
|
||||
)
|
||||
)
|
||||
listRef.current?.scrollTo(0, 0)
|
||||
}
|
||||
if (!visible && wasVisibleRef.current) {
|
||||
|
||||
@@ -97,6 +97,9 @@ export function useWorktreeJumpPaletteStoreState({
|
||||
(state) => state.hideWorkspacesFromOtherDevices
|
||||
)
|
||||
const showSleepingWorkspaces = useAppStore((state) => state.showSleepingWorkspaces)
|
||||
const workspaceHostScope = useAppStore((state) => state.workspaceHostScope)
|
||||
const visibleWorkspaceHostIds = useAppStore((state) => state.visibleWorkspaceHostIds)
|
||||
const filterRepoIds = useAppStore((state) => state.filterRepoIds)
|
||||
const alwaysShowDefaultBranchWorkspace = useAppStore(
|
||||
(state) => state.alwaysShowDefaultBranchWorkspace
|
||||
)
|
||||
@@ -174,6 +177,9 @@ export function useWorktreeJumpPaletteStoreState({
|
||||
hideDetachedHeadWorkspaces,
|
||||
hideWorkspacesFromOtherDevices,
|
||||
showSleepingWorkspaces,
|
||||
workspaceHostScope,
|
||||
visibleWorkspaceHostIds,
|
||||
filterRepoIds,
|
||||
alwaysShowDefaultBranchWorkspace,
|
||||
lastVisitedAtByWorktreeId,
|
||||
workspacePortScan,
|
||||
|
||||
Reference in New Issue
Block a user