mirror of
https://github.com/stablyai/orca.git
synced 2026-09-23 00:02:29 +00:00
"Hide sleeping" swept each project's main workspace out of the sidebar as soon as it had no live PTY, browser tab or agent — even with "Hide default branch" off. For a project whose only row is that workspace (a folder workspace, a fresh clone, a detached-HEAD main), the entire project vanished with no in-place way back. Adds a shared `isSleepingSweepExemptWorkspace` predicate keyed on `isMainWorktree` rather than the branch name, so folder workspaces (no branch), detached-HEAD mains, and SSH rows whose head/branch are blanked while a provider is disconnected all stay put. Wired into `computeVisibleWorktreeIds` (sidebar, Cmd+1-9, workspace board), the jump palette's duplicate inline pass, and mobile's `filterWorktrees`. Ships default-on with an escape hatch: a persisted `alwaysShowDefaultBranchWorkspace` setting surfaced as "Except default branch" under "Hide sleeping". Explicit "Hide default branch" still wins, since it filters before the sleeping sweep. Mobile reads the setting but never writes it back, so a desktop opt-out can't be clobbered by a filter tap before the ui.get roundtrip lands. Combines the two PRs open against #8873. #8966's exempt set is a strict subset of this one, so its production diff was subsumed rather than ported; its jump-palette render harness and e2e spec were carried over, and are the only such coverage here. Fixes #8873 Closes #8966 Co-authored-by: Rod Boev <rod.boev@gmail.com> Co-authored-by: Orca <help@stably.ai>
166 lines
5.4 KiB
TypeScript
166 lines
5.4 KiB
TypeScript
/**
|
|
* Regression #8873: with "Hide sleeping" on, the project's main workspace must
|
|
* stay in the sidebar — it is the only guaranteed way back into the project —
|
|
* while a sleeping feature workspace is still swept.
|
|
*/
|
|
|
|
import type { Page } from '@stablyai/playwright-test'
|
|
import { test, expect } from './helpers/orca-app'
|
|
import { waitForActiveWorktree, waitForSessionReady } from './helpers/store'
|
|
import { worktreeRow } from './worktree-row-locators'
|
|
|
|
type SidebarVisibilityScenario = {
|
|
defaultBranchId: string
|
|
featureId: string
|
|
}
|
|
|
|
async function seedSidebarVisibilityScenario(page: Page): Promise<SidebarVisibilityScenario> {
|
|
return page.evaluate(() => {
|
|
const store = window.__store
|
|
if (!store) {
|
|
throw new Error('window.__store is not available')
|
|
}
|
|
|
|
const state = store.getState()
|
|
const repo = state.repos[0]
|
|
if (!repo) {
|
|
throw new Error('Sidebar visibility E2E needs a seeded repo')
|
|
}
|
|
|
|
const currentWorktree = (state.worktreesByRepo[repo.id] ?? [])[0]
|
|
if (!currentWorktree) {
|
|
throw new Error('Sidebar visibility E2E needs a seeded worktree')
|
|
}
|
|
|
|
const defaultBranchId = 'e2e-default-branch-visibility-main'
|
|
const featureId = 'e2e-default-branch-visibility-feature'
|
|
const currentId = currentWorktree.id
|
|
|
|
store.setState((current) => ({
|
|
worktreesByRepo: {
|
|
...current.worktreesByRepo,
|
|
[repo.id]: [
|
|
{
|
|
...currentWorktree,
|
|
id: currentId,
|
|
displayName: 'Current workspace',
|
|
isMainWorktree: false,
|
|
branch: 'refs/heads/current',
|
|
lastActivityAt: 3
|
|
},
|
|
{
|
|
...currentWorktree,
|
|
id: defaultBranchId,
|
|
displayName: 'Default branch workspace',
|
|
isMainWorktree: true,
|
|
branch: 'refs/heads/main',
|
|
lastActivityAt: 2
|
|
},
|
|
{
|
|
...currentWorktree,
|
|
id: featureId,
|
|
displayName: 'Feature workspace',
|
|
isMainWorktree: false,
|
|
branch: 'refs/heads/feature',
|
|
lastActivityAt: 1
|
|
}
|
|
]
|
|
},
|
|
tabsByWorktree: {
|
|
...current.tabsByWorktree,
|
|
[defaultBranchId]: [],
|
|
[featureId]: []
|
|
},
|
|
browserTabsByWorktree: {
|
|
...current.browserTabsByWorktree,
|
|
[defaultBranchId]: [],
|
|
[featureId]: []
|
|
}
|
|
}))
|
|
|
|
const nextState = store.getState()
|
|
nextState.setActiveView('terminal')
|
|
nextState.setSidebarOpen(true)
|
|
nextState.setGroupBy('none')
|
|
nextState.setSortBy('recent')
|
|
nextState.setShowActiveOnly(false)
|
|
nextState.setFilterRepoIds([])
|
|
|
|
return { defaultBranchId, featureId }
|
|
})
|
|
}
|
|
|
|
test.describe('Default branch visibility', () => {
|
|
test.beforeEach(async ({ orcaPage }) => {
|
|
await waitForSessionReady(orcaPage)
|
|
await waitForActiveWorktree(orcaPage)
|
|
})
|
|
|
|
test('keeps the default branch visible when sleeping workspaces are hidden', async ({
|
|
orcaPage
|
|
}) => {
|
|
const { defaultBranchId, featureId } = await seedSidebarVisibilityScenario(orcaPage)
|
|
const defaultBranchRow = worktreeRow(orcaPage, defaultBranchId)
|
|
const featureRow = worktreeRow(orcaPage, featureId)
|
|
|
|
// Poll rather than set once: hydration can land after the seed and reset the filters.
|
|
await expect
|
|
.poll(() =>
|
|
orcaPage.evaluate(
|
|
({ defaultBranchId, featureId }) => {
|
|
const state = window.__store?.getState()
|
|
state?.setShowSleepingWorkspaces(false)
|
|
state?.setHideDefaultBranchWorkspace(false)
|
|
state?.setAlwaysShowDefaultBranchWorkspace(true)
|
|
const featureTabs = state?.tabsByWorktree[featureId] ?? []
|
|
return {
|
|
alwaysShowDefaultBranchWorkspace: state?.alwaysShowDefaultBranchWorkspace ?? null,
|
|
defaultBranchTabs: state?.tabsByWorktree[defaultBranchId]?.length ?? 0,
|
|
featureBrowserTabs: state?.browserTabsByWorktree[featureId]?.length ?? 0,
|
|
featureHasLivePty: featureTabs.some(
|
|
(tab) => (state?.ptyIdsByTabId[tab.id] ?? []).length > 0
|
|
),
|
|
featureTabs: featureTabs.length,
|
|
hideDefaultBranchWorkspace: state?.hideDefaultBranchWorkspace ?? null,
|
|
showSleepingWorkspaces: state?.showSleepingWorkspaces ?? null
|
|
}
|
|
},
|
|
{ defaultBranchId, featureId }
|
|
)
|
|
)
|
|
.toEqual({
|
|
alwaysShowDefaultBranchWorkspace: true,
|
|
defaultBranchTabs: 0,
|
|
featureBrowserTabs: 0,
|
|
featureHasLivePty: false,
|
|
featureTabs: 0,
|
|
hideDefaultBranchWorkspace: false,
|
|
showSleepingWorkspaces: false
|
|
})
|
|
|
|
await expect(defaultBranchRow).toBeVisible()
|
|
await expect(defaultBranchRow).toContainText('Default branch workspace')
|
|
await expect(featureRow).toHaveCount(0)
|
|
|
|
// Opting out of the exemption is the only way back to the pre-#8873 sweep.
|
|
await orcaPage.evaluate(() => {
|
|
window.__store?.getState().setAlwaysShowDefaultBranchWorkspace(false)
|
|
})
|
|
|
|
await expect(defaultBranchRow).toHaveCount(0)
|
|
|
|
await orcaPage.evaluate(() => {
|
|
window.__store?.getState().setAlwaysShowDefaultBranchWorkspace(true)
|
|
})
|
|
|
|
await expect(defaultBranchRow).toBeVisible()
|
|
|
|
// The explicit hide filter still outranks the exemption.
|
|
await orcaPage.evaluate(() => {
|
|
window.__store?.getState().setHideDefaultBranchWorkspace(true)
|
|
})
|
|
|
|
await expect(defaultBranchRow).toHaveCount(0)
|
|
})
|
|
})
|