mirror of
https://github.com/stablyai/orca.git
synced 2026-10-20 16:03:20 +00:00
Show the "Except default branch" filter only when "Hide sleeping" is on
The exemption only takes effect during the hide-sleeping sweep, so its row now renders (and counts toward the filter badge) only while its parent toggle is ticked. Co-authored-by: Orca <help@stably.ai>
This commit is contained in:
@@ -31,6 +31,7 @@ import { FilterToggleRow } from './FilterToggleRow'
|
||||
import { useShortcutLabel } from '@/hooks/useShortcutLabel'
|
||||
import { searchRepos } from '@/lib/repo-search'
|
||||
import { DEFAULT_SHOW_SLEEPING_WORKSPACES } from '../../../../shared/constants'
|
||||
import { isSleepingSweepExemptionNarrowingList } from './visible-worktrees'
|
||||
import { translate } from '@/i18n/i18n'
|
||||
|
||||
type SidebarFilterProps = {
|
||||
@@ -111,15 +112,19 @@ const SidebarFilter = React.memo(function SidebarFilter({
|
||||
const selectedCount = selectedRepoIdSet.size
|
||||
const hasRepoFilter = selectedCount > 0
|
||||
const hasSleepingFilter = showSleepingWorkspaces !== DEFAULT_SHOW_SLEEPING_WORKSPACES
|
||||
// Why counted: turning the exemption off is the only way that row narrows the
|
||||
// list — but only while its parent row is on, which is also when it renders.
|
||||
const hasSleepingExemptionFilter = isSleepingSweepExemptionNarrowingList(
|
||||
showSleepingWorkspaces,
|
||||
alwaysShowDefaultBranchWorkspace
|
||||
)
|
||||
const hasAnyFilter =
|
||||
hasSleepingFilter ||
|
||||
hideDefaultBranchWorkspace ||
|
||||
hideAutomationGeneratedWorkspaces ||
|
||||
hideCliCreatedWorkspaces ||
|
||||
hideDetachedHeadWorkspaces ||
|
||||
// Why counted: turning the exemption off is the only way this row narrows
|
||||
// the list, so the badge and Reset filters must both notice it.
|
||||
!alwaysShowDefaultBranchWorkspace ||
|
||||
hasSleepingExemptionFilter ||
|
||||
hasRepoFilter
|
||||
const activeFilterCount =
|
||||
(hasSleepingFilter ? 1 : 0) +
|
||||
@@ -127,7 +132,7 @@ const SidebarFilter = React.memo(function SidebarFilter({
|
||||
(hideAutomationGeneratedWorkspaces ? 1 : 0) +
|
||||
(hideCliCreatedWorkspaces ? 1 : 0) +
|
||||
(hideDetachedHeadWorkspaces ? 1 : 0) +
|
||||
(alwaysShowDefaultBranchWorkspace ? 0 : 1) +
|
||||
(hasSleepingExemptionFilter ? 1 : 0) +
|
||||
selectedCount
|
||||
|
||||
const filteredRepos = useMemo(() => searchRepos(repos, query), [repos, query])
|
||||
@@ -221,20 +226,24 @@ const SidebarFilter = React.memo(function SidebarFilter({
|
||||
onChange={(hideSleeping) => setShowSleepingWorkspaces(!hideSleeping)}
|
||||
shortcutLabel={sleepingShortcut === 'Unassigned' ? undefined : sleepingShortcut}
|
||||
/>
|
||||
<FilterToggleRow
|
||||
indented
|
||||
icon={<GitBranch className="size-3.5" />}
|
||||
label={translate(
|
||||
'auto.components.sidebar.SidebarFilter.keepDefaultBranch',
|
||||
'Except default branch'
|
||||
)}
|
||||
ariaLabel={translate(
|
||||
'auto.components.sidebar.SidebarFilter.keepDefaultBranchAria',
|
||||
'Keep the default branch visible while hiding sleeping workspaces'
|
||||
)}
|
||||
checked={alwaysShowDefaultBranchWorkspace}
|
||||
onChange={setAlwaysShowDefaultBranchWorkspace}
|
||||
/>
|
||||
{/* Why gated: the exemption only has an effect while sleeping workspaces
|
||||
are being swept, so it stays hidden until its parent row is on. */}
|
||||
{!showSleepingWorkspaces && (
|
||||
<FilterToggleRow
|
||||
indented
|
||||
icon={<GitBranch className="size-3.5" />}
|
||||
label={translate(
|
||||
'auto.components.sidebar.SidebarFilter.keepDefaultBranch',
|
||||
'Except default branch'
|
||||
)}
|
||||
ariaLabel={translate(
|
||||
'auto.components.sidebar.SidebarFilter.keepDefaultBranchAria',
|
||||
'Keep the default branch visible while hiding sleeping workspaces'
|
||||
)}
|
||||
checked={alwaysShowDefaultBranchWorkspace}
|
||||
onChange={setAlwaysShowDefaultBranchWorkspace}
|
||||
/>
|
||||
)}
|
||||
<FilterToggleRow
|
||||
icon={<GitBranch className="size-3.5" />}
|
||||
label={translate(
|
||||
|
||||
@@ -0,0 +1,86 @@
|
||||
// @vitest-environment happy-dom
|
||||
|
||||
/**
|
||||
* The "Except default branch" exemption only bites during the "Hide sleeping"
|
||||
* sweep, so its row must stay out of the filter list until the parent row is on.
|
||||
*/
|
||||
|
||||
import { act } from 'react'
|
||||
import { createRoot, type Root } from 'react-dom/client'
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
|
||||
const mocks = vi.hoisted(() => ({
|
||||
state: {} as Record<string, unknown>
|
||||
}))
|
||||
|
||||
vi.mock('@/store', () => ({
|
||||
useAppStore: (selector: (state: Record<string, unknown>) => unknown) => selector(mocks.state)
|
||||
}))
|
||||
|
||||
import SidebarWorkspaceFilterSection from './SidebarWorkspaceFilterSection'
|
||||
|
||||
const EXEMPTION_LABEL = 'Except default branch'
|
||||
|
||||
function setState(overrides: Record<string, unknown> = {}): void {
|
||||
mocks.state = {
|
||||
showSleepingWorkspaces: true,
|
||||
setShowSleepingWorkspaces: vi.fn(),
|
||||
hideDefaultBranchWorkspace: false,
|
||||
setHideDefaultBranchWorkspace: vi.fn(),
|
||||
hideAutomationGeneratedWorkspaces: false,
|
||||
setHideAutomationGeneratedWorkspaces: vi.fn(),
|
||||
hideCliCreatedWorkspaces: false,
|
||||
setHideCliCreatedWorkspaces: vi.fn(),
|
||||
hideDetachedHeadWorkspaces: false,
|
||||
setHideDetachedHeadWorkspaces: vi.fn(),
|
||||
alwaysShowDefaultBranchWorkspace: true,
|
||||
setAlwaysShowDefaultBranchWorkspace: vi.fn(),
|
||||
...overrides
|
||||
}
|
||||
}
|
||||
|
||||
let container: HTMLDivElement
|
||||
let root: Root
|
||||
|
||||
function render(): void {
|
||||
act(() => {
|
||||
root.render(<SidebarWorkspaceFilterSection />)
|
||||
})
|
||||
}
|
||||
|
||||
function rowLabels(): string[] {
|
||||
return Array.from(container.querySelectorAll('[role="switch"]')).map(
|
||||
(el) => el.textContent?.trim() ?? ''
|
||||
)
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
container = document.createElement('div')
|
||||
document.body.appendChild(container)
|
||||
root = createRoot(container)
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
act(() => root.unmount())
|
||||
container.remove()
|
||||
})
|
||||
|
||||
describe('SidebarWorkspaceFilterSection', () => {
|
||||
it('hides the default-branch exemption row while sleeping workspaces are shown', () => {
|
||||
setState({ showSleepingWorkspaces: true })
|
||||
render()
|
||||
expect(rowLabels()).not.toContain(EXEMPTION_LABEL)
|
||||
})
|
||||
|
||||
it('shows the exemption row once "Hide sleeping" is ticked', () => {
|
||||
setState({ showSleepingWorkspaces: false })
|
||||
render()
|
||||
expect(rowLabels()).toContain(EXEMPTION_LABEL)
|
||||
})
|
||||
|
||||
it('keeps the exemption row hidden even when it is switched off', () => {
|
||||
setState({ showSleepingWorkspaces: true, alwaysShowDefaultBranchWorkspace: false })
|
||||
render()
|
||||
expect(rowLabels()).not.toContain(EXEMPTION_LABEL)
|
||||
})
|
||||
})
|
||||
@@ -38,20 +38,24 @@ const SidebarWorkspaceFilterSection = React.memo(function SidebarWorkspaceFilter
|
||||
checked={!showSleepingWorkspaces}
|
||||
onChange={(hideSleeping) => setShowSleepingWorkspaces(!hideSleeping)}
|
||||
/>
|
||||
<FilterToggleRow
|
||||
indented
|
||||
icon={<GitBranch className="size-3.5" />}
|
||||
label={translate(
|
||||
'auto.components.sidebar.SidebarWorkspaceFilterSection.keepDefaultBranch',
|
||||
'Except default branch'
|
||||
)}
|
||||
ariaLabel={translate(
|
||||
'auto.components.sidebar.SidebarWorkspaceFilterSection.keepDefaultBranchAria',
|
||||
'Keep the default branch visible while hiding sleeping workspaces'
|
||||
)}
|
||||
checked={alwaysShowDefaultBranchWorkspace}
|
||||
onChange={setAlwaysShowDefaultBranchWorkspace}
|
||||
/>
|
||||
{/* Why gated: the exemption only has an effect while sleeping workspaces
|
||||
are being swept, so it stays hidden until its parent row is on. */}
|
||||
{!showSleepingWorkspaces && (
|
||||
<FilterToggleRow
|
||||
indented
|
||||
icon={<GitBranch className="size-3.5" />}
|
||||
label={translate(
|
||||
'auto.components.sidebar.SidebarWorkspaceFilterSection.keepDefaultBranch',
|
||||
'Except default branch'
|
||||
)}
|
||||
ariaLabel={translate(
|
||||
'auto.components.sidebar.SidebarWorkspaceFilterSection.keepDefaultBranchAria',
|
||||
'Keep the default branch visible while hiding sleeping workspaces'
|
||||
)}
|
||||
checked={alwaysShowDefaultBranchWorkspace}
|
||||
onChange={setAlwaysShowDefaultBranchWorkspace}
|
||||
/>
|
||||
)}
|
||||
<FilterToggleRow
|
||||
icon={<GitBranch className="size-3.5" />}
|
||||
label={translate(
|
||||
|
||||
@@ -16,6 +16,7 @@ import {
|
||||
} from '@/components/ui/dropdown-menu'
|
||||
import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip'
|
||||
import { DEFAULT_SHOW_SLEEPING_WORKSPACES } from '../../../../shared/constants'
|
||||
import { isSleepingSweepExemptionNarrowingList } from './visible-worktrees'
|
||||
import SidebarRepositoryFilterSection from './SidebarRepositoryFilterSection'
|
||||
import SidebarWorkspaceFilterSection from './SidebarWorkspaceFilterSection'
|
||||
import { getSidebarHostVisibilityLabel, shouldShowHostScopeControls } from './sidebar-host-options'
|
||||
@@ -79,13 +80,19 @@ const SidebarWorkspaceOptionsMenu = React.memo(function SidebarWorkspaceOptionsM
|
||||
const hasRepoFilter = selectedCount > 0
|
||||
const hasSleepingFilter = showSleepingWorkspaces !== DEFAULT_SHOW_SLEEPING_WORKSPACES
|
||||
const hasHostVisibilityFilter = visibleWorkspaceHostIds !== null
|
||||
// Why gated on the parent row: the exemption only narrows the list during the
|
||||
// "Hide sleeping" sweep, which is also the only time its row is rendered.
|
||||
const hasSleepingExemptionFilter = isSleepingSweepExemptionNarrowingList(
|
||||
showSleepingWorkspaces,
|
||||
alwaysShowDefaultBranchWorkspace
|
||||
)
|
||||
const hasAnyFilter =
|
||||
hasSleepingFilter ||
|
||||
hideDefaultBranchWorkspace ||
|
||||
hideAutomationGeneratedWorkspaces ||
|
||||
hideCliCreatedWorkspaces ||
|
||||
hideDetachedHeadWorkspaces ||
|
||||
!alwaysShowDefaultBranchWorkspace ||
|
||||
hasSleepingExemptionFilter ||
|
||||
hasRepoFilter ||
|
||||
hasHostVisibilityFilter
|
||||
const activeFilterCount =
|
||||
@@ -94,7 +101,7 @@ const SidebarWorkspaceOptionsMenu = React.memo(function SidebarWorkspaceOptionsM
|
||||
(hideAutomationGeneratedWorkspaces ? 1 : 0) +
|
||||
(hideCliCreatedWorkspaces ? 1 : 0) +
|
||||
(hideDetachedHeadWorkspaces ? 1 : 0) +
|
||||
(alwaysShowDefaultBranchWorkspace ? 0 : 1) +
|
||||
(hasSleepingExemptionFilter ? 1 : 0) +
|
||||
(hasHostVisibilityFilter ? 1 : 0) +
|
||||
selectedCount
|
||||
const activeFilterLabel = `${activeFilterCount} ${activeFilterCount === 1 ? 'filter' : 'filters'}`
|
||||
|
||||
@@ -2,6 +2,7 @@ import { describe, expect, it } from 'vitest'
|
||||
import {
|
||||
computeClearFilterActions,
|
||||
isDefaultBranchWorkspace,
|
||||
isSleepingSweepExemptionNarrowingList,
|
||||
sidebarHasActiveFilters
|
||||
} from './visible-worktrees'
|
||||
import type { Worktree } from '../../../../shared/types'
|
||||
@@ -114,6 +115,21 @@ describe('sidebarHasActiveFilters', () => {
|
||||
})
|
||||
})
|
||||
|
||||
describe('isSleepingSweepExemptionNarrowingList', () => {
|
||||
it('is false while sleeping workspaces are shown, even when opted out', () => {
|
||||
expect(isSleepingSweepExemptionNarrowingList(true, false)).toBe(false)
|
||||
})
|
||||
|
||||
it('is true only when the sweep is on and the exemption is opted out', () => {
|
||||
expect(isSleepingSweepExemptionNarrowingList(false, false)).toBe(true)
|
||||
expect(isSleepingSweepExemptionNarrowingList(false, true)).toBe(false)
|
||||
})
|
||||
|
||||
it('treats a missing flag as the on default', () => {
|
||||
expect(isSleepingSweepExemptionNarrowingList(false, undefined)).toBe(false)
|
||||
})
|
||||
})
|
||||
|
||||
describe('computeClearFilterActions', () => {
|
||||
it('returns no-op actions when nothing is set', () => {
|
||||
expect(computeClearFilterActions(filterState())).toEqual({
|
||||
|
||||
@@ -54,6 +54,18 @@ export function isSleepingSweepExemptWorkspace(
|
||||
return alwaysShowDefaultBranchWorkspace !== false && worktree.isMainWorktree
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether turning the exemption off is currently narrowing the list. It only
|
||||
* bites during the "Hide sleeping" sweep, so its row — and the filter badge on
|
||||
* both menu surfaces — ignores it while sleeping workspaces are shown.
|
||||
*/
|
||||
export function isSleepingSweepExemptionNarrowingList(
|
||||
showSleepingWorkspaces: boolean,
|
||||
alwaysShowDefaultBranchWorkspace: boolean | undefined
|
||||
): boolean {
|
||||
return !showSleepingWorkspaces && alwaysShowDefaultBranchWorkspace === false
|
||||
}
|
||||
|
||||
export function isAutomationGeneratedWorkspace(worktree: Worktree): boolean {
|
||||
return worktree.automationProvenance?.kind === 'created-by-automation'
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user