fix(sidebar): preserve meaningful workspace status indicators (#12658)
This commit is contained in:
parent
003114dfad
commit
e5f49e0e1d
|
|
@ -60,7 +60,7 @@ vi.mock('@/components/ui/tooltip', () => ({
|
|||
}))
|
||||
|
||||
vi.mock('./use-worktree-activity-status', () => ({
|
||||
useWorktreeActivityStatus: () => 'active'
|
||||
useWorktreeActivityStatus: () => 'inactive'
|
||||
}))
|
||||
|
||||
vi.mock('./CacheTimer', () => ({
|
||||
|
|
@ -171,8 +171,8 @@ describe('WorktreeCard linked PR display', () => {
|
|||
<WorktreeCard worktree={makeWorktree({ linkedPR: 456 })} repo={makeRepo()} isActive={false} />
|
||||
)
|
||||
|
||||
expect(markup).toContain('Active')
|
||||
expect(markup).toContain('bg-emerald-500')
|
||||
expect(markup).toContain('Inactive')
|
||||
expect(markup).toContain('bg-neutral-500/40')
|
||||
expect(markup).not.toContain('PR: Open')
|
||||
expect(markup).not.toContain('Linked PR #456')
|
||||
}, 20_000)
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ let tabsByWorktree: Record<string, { id: string }[]> = {}
|
|||
let ptyIdsByTabId: Record<string, string[]> = {}
|
||||
let browserTabsByWorktree: Record<string, { id: string }[]> = {}
|
||||
let settings: Partial<GlobalSettings> | null = null
|
||||
let activityStatus = 'idle'
|
||||
let projectGroups: unknown[] = []
|
||||
let workspaceDeleteModifierPressed = false
|
||||
let gitConflictOperationByWorktree: Record<string, GitConflictOperation> = {}
|
||||
|
|
@ -60,7 +61,7 @@ vi.mock('@/components/ui/tooltip', () => ({
|
|||
}))
|
||||
|
||||
vi.mock('./use-worktree-activity-status', () => ({
|
||||
useWorktreeActivityStatus: () => 'idle'
|
||||
useWorktreeActivityStatus: () => activityStatus
|
||||
}))
|
||||
|
||||
vi.mock('./CacheTimer', () => ({
|
||||
|
|
@ -144,6 +145,7 @@ describe('WorktreeCard quick actions', () => {
|
|||
projectGroups = []
|
||||
workspaceDeleteModifierPressed = false
|
||||
gitConflictOperationByWorktree = {}
|
||||
activityStatus = 'idle'
|
||||
})
|
||||
|
||||
it('marks the unread toggle as a workspace-board-preserving action', () => {
|
||||
|
|
@ -252,6 +254,19 @@ describe('WorktreeCard quick actions', () => {
|
|||
expect(markup).not.toContain('lucide-git-branch')
|
||||
})
|
||||
|
||||
it('keeps the quiet status dot when the branch card property is off', () => {
|
||||
settings = { experimentalNewWorktreeCardStyle: true }
|
||||
worktreeCardProperties = ['status']
|
||||
activityStatus = 'inactive'
|
||||
|
||||
const markup = renderToStaticMarkup(
|
||||
<WorktreeCard worktree={makeWorktree()} repo={makeRepo()} isActive={false} />
|
||||
)
|
||||
|
||||
expect(markup).toContain('bg-neutral-500/40')
|
||||
expect(markup).not.toContain('lucide-git-branch')
|
||||
})
|
||||
|
||||
it('does not render a pending first-agent rename title badge', () => {
|
||||
const markup = renderToStaticMarkup(
|
||||
<WorktreeCard
|
||||
|
|
|
|||
|
|
@ -1406,7 +1406,7 @@ const WorktreeCard = React.memo(function WorktreeCard({
|
|||
onToggleUnread={handleToggleUnreadQuick}
|
||||
prDisplay={statusLaneReview}
|
||||
newCardStyle={newCardStyle}
|
||||
hasBranchIdentity={Boolean(branchIdentityDisplay)}
|
||||
hasBranchIdentity={hasPathIdentityEnabled && Boolean(branchIdentityDisplay)}
|
||||
/>
|
||||
</div>
|
||||
) : null}
|
||||
|
|
|
|||
|
|
@ -192,7 +192,7 @@ describe('WorktreeCardStatusSlot', () => {
|
|||
expect(markup).not.toContain('PR checks: Failed')
|
||||
})
|
||||
|
||||
it('uses PR status instead of the quiet active dot when new card style is on', () => {
|
||||
it('keeps the active dot ahead of PR status when new card style is on', () => {
|
||||
const markup = renderToStaticMarkup(
|
||||
<WorktreeCardStatusSlot
|
||||
worktreeId="wt-1"
|
||||
|
|
@ -207,14 +207,34 @@ describe('WorktreeCardStatusSlot', () => {
|
|||
/>
|
||||
)
|
||||
|
||||
expect(markup).toContain('PR checks: Failed')
|
||||
expect(markup).toContain('Active')
|
||||
expect(markup).toContain('inline-flex size-5 items-center justify-center')
|
||||
expect(markup).toContain('size-[13px] translate-x-px')
|
||||
expect(markup).toContain('text-rose-500/85')
|
||||
expect(markup).not.toContain('bg-emerald-500')
|
||||
expect(markup).toContain('bg-emerald-500')
|
||||
expect(markup).not.toContain('PR checks: Failed')
|
||||
})
|
||||
|
||||
it('keeps the emerald activity dot ahead of branch identity when new card style is on', () => {
|
||||
const markup = renderToStaticMarkup(
|
||||
<WorktreeCardStatusSlot
|
||||
worktreeId="wt-1"
|
||||
showStatus
|
||||
showUnreadAction={false}
|
||||
isUnread={false}
|
||||
unreadTooltip="Mark as unread"
|
||||
onPointerDown={vi.fn()}
|
||||
onToggleUnread={vi.fn()}
|
||||
newCardStyle
|
||||
hasBranchIdentity
|
||||
/>
|
||||
)
|
||||
|
||||
expect(markup).toContain('Active')
|
||||
expect(markup).toContain('bg-emerald-500')
|
||||
expect(markup).not.toContain('lucide-git-branch')
|
||||
})
|
||||
|
||||
it('uses the unified compact review glyph for GitLab MR status', () => {
|
||||
mocks.status = 'inactive'
|
||||
const markup = renderToStaticMarkup(
|
||||
<WorktreeCardStatusSlot
|
||||
worktreeId="wt-1"
|
||||
|
|
@ -236,7 +256,7 @@ describe('WorktreeCardStatusSlot', () => {
|
|||
expect(markup).not.toContain('lucide-git-merge')
|
||||
})
|
||||
|
||||
it('uses PR status instead of the quiet done dot when new card style is on', () => {
|
||||
it('keeps the done dot ahead of PR status when new card style is on', () => {
|
||||
mocks.status = 'done'
|
||||
const markup = renderToStaticMarkup(
|
||||
<WorktreeCardStatusSlot
|
||||
|
|
@ -252,8 +272,9 @@ describe('WorktreeCardStatusSlot', () => {
|
|||
/>
|
||||
)
|
||||
|
||||
expect(markup).toContain('PR checks: Failed')
|
||||
expect(markup).not.toContain('bg-emerald-500')
|
||||
expect(markup).toContain('Done')
|
||||
expect(markup).toContain('bg-emerald-500')
|
||||
expect(markup).not.toContain('PR checks: Failed')
|
||||
})
|
||||
|
||||
it('uses PR status instead of the inactive dot when new card style is on', () => {
|
||||
|
|
@ -277,7 +298,8 @@ describe('WorktreeCardStatusSlot', () => {
|
|||
expect(markup).not.toContain('bg-neutral-500/40')
|
||||
})
|
||||
|
||||
it('uses a branch icon with branch-only tooltip copy by default', () => {
|
||||
it('uses a branch icon with branch-only tooltip copy on quiet rows', () => {
|
||||
mocks.status = 'inactive'
|
||||
const markup = renderToStaticMarkup(
|
||||
<WorktreeCardStatusSlot
|
||||
worktreeId="wt-1"
|
||||
|
|
@ -301,6 +323,7 @@ describe('WorktreeCardStatusSlot', () => {
|
|||
})
|
||||
|
||||
it('uses context-aware branch or folder path tooltip copy', () => {
|
||||
mocks.status = 'inactive'
|
||||
const markup = renderToStaticMarkup(
|
||||
<WorktreeCardStatusSlot
|
||||
worktreeId="wt-1"
|
||||
|
|
@ -407,6 +430,7 @@ describe('WorktreeCardStatusSlot', () => {
|
|||
})
|
||||
|
||||
it('overlays an unread badge on PR status when new card style is on', () => {
|
||||
mocks.status = 'inactive'
|
||||
const markup = renderToStaticMarkup(
|
||||
<WorktreeCardStatusSlot
|
||||
worktreeId="wt-1"
|
||||
|
|
@ -436,6 +460,7 @@ describe('WorktreeCardStatusSlot', () => {
|
|||
})
|
||||
|
||||
it('overlays an unread badge on the branch icon in new card style', () => {
|
||||
mocks.status = 'inactive'
|
||||
const markup = renderToStaticMarkup(
|
||||
<WorktreeCardStatusSlot
|
||||
worktreeId="wt-1"
|
||||
|
|
|
|||
|
|
@ -25,7 +25,8 @@ type WorktreeCardStatusSlotProps = {
|
|||
className?: string
|
||||
}
|
||||
|
||||
const QUIET_REVIEW_REPLACEABLE_STATUSES = new Set<WorktreeStatus>(['active', 'done', 'inactive'])
|
||||
// Passive identity only replaces status when the workspace is inactive.
|
||||
const IDENTITY_REPLACEABLE_STATUSES = new Set<WorktreeStatus>(['inactive'])
|
||||
// Why: a missing review display can also mean provider state is unavailable,
|
||||
// so the passive label names the identity cue without claiming no review exists.
|
||||
function getDefaultBranchIdentityLabel(): string {
|
||||
|
|
@ -103,16 +104,13 @@ export function WorktreeCardStatusSlot({
|
|||
const status = useWorktreeActivityStatus(worktreeId)
|
||||
const statusLabel = getWorktreeStatusLabel(status) || status
|
||||
const canShowReviewStatus =
|
||||
newCardStyle &&
|
||||
showStatus &&
|
||||
prDisplay !== null &&
|
||||
QUIET_REVIEW_REPLACEABLE_STATUSES.has(status)
|
||||
newCardStyle && showStatus && prDisplay !== null && IDENTITY_REPLACEABLE_STATUSES.has(status)
|
||||
const canShowBranchStatus =
|
||||
newCardStyle &&
|
||||
showStatus &&
|
||||
hasBranchIdentity &&
|
||||
prDisplay === null &&
|
||||
QUIET_REVIEW_REPLACEABLE_STATUSES.has(status)
|
||||
IDENTITY_REPLACEABLE_STATUSES.has(status)
|
||||
const passiveStatusLabel =
|
||||
canShowReviewStatus && prDisplay
|
||||
? getReviewStatusTooltip(prDisplay)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,84 @@
|
|||
// Regression coverage for active status taking precedence over passive identity (#8813).
|
||||
|
||||
import type { Locator, Page } from '@stablyai/playwright-test'
|
||||
import { test, expect } from './helpers/orca-app'
|
||||
import {
|
||||
waitForSessionReady,
|
||||
waitForActiveWorktree,
|
||||
getAllWorktreeIds,
|
||||
ensureTerminalVisible
|
||||
} from './helpers/store'
|
||||
import { worktreeRow, worktreeRowSurface } from './worktree-row-locators'
|
||||
|
||||
function statusLane(page: Page, worktreeId: string): Locator {
|
||||
return worktreeRow(page, worktreeId).locator('[data-worktree-card-status-slot]').first()
|
||||
}
|
||||
|
||||
function statusDot(page: Page, worktreeId: string): Locator {
|
||||
return statusLane(page, worktreeId).locator('span.bg-emerald-500').first()
|
||||
}
|
||||
|
||||
function branchIdentityGlyph(page: Page, worktreeId: string): Locator {
|
||||
return statusLane(page, worktreeId).locator('svg.lucide-git-branch')
|
||||
}
|
||||
|
||||
// PTY liveness, not the tab row, drives the activity heuristic.
|
||||
async function waitForLivePty(page: Page, worktreeId: string): Promise<void> {
|
||||
await expect
|
||||
.poll(
|
||||
async () =>
|
||||
page.evaluate((id) => {
|
||||
const state = window.__store!.getState()
|
||||
return (state.tabsByWorktree[id] ?? []).some(
|
||||
(tab) => (state.ptyIdsByTabId[tab.id] ?? []).length > 0
|
||||
)
|
||||
}, worktreeId),
|
||||
{ timeout: 30_000, message: `No live PTY attached for worktree ${worktreeId}` }
|
||||
)
|
||||
.toBe(true)
|
||||
}
|
||||
|
||||
test.describe('Worktree card status indicator', () => {
|
||||
test.beforeEach(async ({ orcaPage }) => {
|
||||
await waitForSessionReady(orcaPage)
|
||||
await waitForActiveWorktree(orcaPage)
|
||||
})
|
||||
|
||||
test('paints the emerald Active dot instead of the grey branch glyph once a workspace goes live', async ({
|
||||
orcaPage
|
||||
}) => {
|
||||
const liveWorktreeId = await waitForActiveWorktree(orcaPage)
|
||||
await ensureTerminalVisible(orcaPage)
|
||||
await waitForLivePty(orcaPage, liveWorktreeId)
|
||||
|
||||
// The regression requires both the new card style and branch identity.
|
||||
await orcaPage.evaluate(async () => {
|
||||
const state = window.__store!.getState()
|
||||
await state.updateSettings({ experimentalNewWorktreeCardStyle: true })
|
||||
state.setWorktreeCardProperties(['status', 'unread', 'branch'])
|
||||
})
|
||||
|
||||
const quietWorktreeId = (await getAllWorktreeIds(orcaPage)).find((id) => id !== liveWorktreeId)
|
||||
if (!quietWorktreeId) {
|
||||
throw new Error('Seeded repo did not expose a second worktree to keep quiet')
|
||||
}
|
||||
|
||||
await expect(branchIdentityGlyph(orcaPage, quietWorktreeId)).toBeVisible()
|
||||
await expect(statusLane(orcaPage, quietWorktreeId)).toHaveText('Branch')
|
||||
|
||||
await expect(statusDot(orcaPage, liveWorktreeId)).toBeVisible()
|
||||
await expect(statusLane(orcaPage, liveWorktreeId)).toHaveText('Active')
|
||||
await expect(branchIdentityGlyph(orcaPage, liveWorktreeId)).toHaveCount(0)
|
||||
|
||||
await worktreeRowSurface(orcaPage, quietWorktreeId).click()
|
||||
await expect
|
||||
.poll(async () => orcaPage.evaluate(() => window.__store!.getState().activeWorktreeId))
|
||||
.toBe(quietWorktreeId)
|
||||
await ensureTerminalVisible(orcaPage)
|
||||
await waitForLivePty(orcaPage, quietWorktreeId)
|
||||
|
||||
await expect(statusDot(orcaPage, quietWorktreeId)).toBeVisible()
|
||||
await expect(statusLane(orcaPage, quietWorktreeId)).toHaveText('Active')
|
||||
await expect(branchIdentityGlyph(orcaPage, quietWorktreeId)).toHaveCount(0)
|
||||
})
|
||||
})
|
||||
Loading…
Reference in New Issue