Show branch cues in experimental worktree cards (#5737)
This commit is contained in:
parent
afde3a3741
commit
fe8b60b11c
|
|
@ -1209,6 +1209,7 @@ const WorktreeCard = React.memo(function WorktreeCard({
|
|||
onToggleUnread={handleToggleUnreadQuick}
|
||||
prDisplay={hoverReview}
|
||||
newCardStyle={newCardStyle}
|
||||
hasBranchIdentity={!isFolder && branch.length > 0}
|
||||
/>
|
||||
</div>
|
||||
) : null}
|
||||
|
|
|
|||
|
|
@ -148,6 +148,46 @@ describe('WorktreeCardStatusSlot', () => {
|
|||
expect(markup).not.toContain('bg-neutral-500/40')
|
||||
})
|
||||
|
||||
it('uses a branch icon instead of the quiet active dot when new card style has no review', () => {
|
||||
const markup = renderToStaticMarkup(
|
||||
<WorktreeCardStatusSlot
|
||||
worktreeId="wt-1"
|
||||
showStatus
|
||||
showUnreadAction={false}
|
||||
isUnread={false}
|
||||
unreadTooltip="Mark as unread"
|
||||
onPointerDown={vi.fn()}
|
||||
onToggleUnread={vi.fn()}
|
||||
newCardStyle
|
||||
/>
|
||||
)
|
||||
|
||||
expect(markup).toContain('Branch')
|
||||
expect(markup).toContain('lucide-git-branch')
|
||||
expect(markup).toContain('text-muted-foreground/70')
|
||||
expect(markup).not.toContain('bg-emerald-500')
|
||||
})
|
||||
|
||||
it('keeps the quiet dot when the row has no branch identity', () => {
|
||||
const markup = renderToStaticMarkup(
|
||||
<WorktreeCardStatusSlot
|
||||
worktreeId="wt-1"
|
||||
showStatus
|
||||
showUnreadAction={false}
|
||||
isUnread={false}
|
||||
unreadTooltip="Mark as unread"
|
||||
onPointerDown={vi.fn()}
|
||||
onToggleUnread={vi.fn()}
|
||||
newCardStyle
|
||||
hasBranchIdentity={false}
|
||||
/>
|
||||
)
|
||||
|
||||
expect(markup).toContain('Active')
|
||||
expect(markup).toContain('bg-emerald-500')
|
||||
expect(markup).not.toContain('lucide-git-branch')
|
||||
})
|
||||
|
||||
it('keeps working activity ahead of PR status in new card style', () => {
|
||||
mocks.status = 'working'
|
||||
const markup = renderToStaticMarkup(
|
||||
|
|
@ -237,23 +277,26 @@ describe('WorktreeCardStatusSlot', () => {
|
|||
expect(markup).not.toContain('bg-emerald-500')
|
||||
})
|
||||
|
||||
it('keeps the new style unread status dot in the stable lane footprint', () => {
|
||||
it('overlays unread on the no-review branch icon in new card style', () => {
|
||||
const markup = renderToStaticMarkup(
|
||||
<WorktreeCardStatusSlot
|
||||
worktreeId="wt-1"
|
||||
showStatus
|
||||
showUnreadAction
|
||||
isUnread={false}
|
||||
unreadTooltip="Mark as unread"
|
||||
isUnread
|
||||
unreadTooltip="Mark as read"
|
||||
onPointerDown={vi.fn()}
|
||||
onToggleUnread={vi.fn()}
|
||||
newCardStyle
|
||||
/>
|
||||
)
|
||||
|
||||
expect(markup).toContain('Active · Mark as unread')
|
||||
expect(markup).toContain('Branch · Mark as read')
|
||||
expect(markup).toContain(
|
||||
'group/unread relative flex cursor-pointer items-center justify-center rounded transition-all size-5'
|
||||
)
|
||||
expect(markup).toContain('lucide-git-branch')
|
||||
expect(markup).toContain('absolute -right-1 -top-1 size-[13px] text-amber-500')
|
||||
expect(markup).not.toContain('bg-emerald-500')
|
||||
})
|
||||
})
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import React from 'react'
|
||||
import { Bell } from 'lucide-react'
|
||||
import { Bell, GitBranch } from 'lucide-react'
|
||||
import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip'
|
||||
import { cn } from '@/lib/utils'
|
||||
import { getWorktreeStatusLabel, type WorktreeStatus } from '@/lib/worktree-status'
|
||||
|
|
@ -19,10 +19,15 @@ type WorktreeCardStatusSlotProps = {
|
|||
onPointerDown: React.PointerEventHandler<HTMLButtonElement>
|
||||
prDisplay?: WorktreeCardPrDisplay | null
|
||||
newCardStyle?: boolean
|
||||
hasBranchIdentity?: boolean
|
||||
className?: string
|
||||
}
|
||||
|
||||
const QUIET_REVIEW_REPLACEABLE_STATUSES = new Set<WorktreeStatus>(['active', 'done', 'inactive'])
|
||||
// Why: a missing review display can also mean provider state is unavailable,
|
||||
// so the passive label names the branch cue without claiming no review exists.
|
||||
const BRANCH_STATUS_LABEL = 'Branch'
|
||||
const branchStatusIconClassName = 'size-4 text-muted-foreground/70'
|
||||
|
||||
function getReviewStatusTooltip(review: WorktreeCardPrDisplay): string {
|
||||
const label = getReviewLabel(review)
|
||||
|
|
@ -57,6 +62,7 @@ export function WorktreeCardStatusSlot({
|
|||
onPointerDown,
|
||||
prDisplay = null,
|
||||
newCardStyle = false,
|
||||
hasBranchIdentity = true,
|
||||
className
|
||||
}: WorktreeCardStatusSlotProps): React.JSX.Element | null {
|
||||
const status = useWorktreeActivityStatus(worktreeId)
|
||||
|
|
@ -66,9 +72,20 @@ export function WorktreeCardStatusSlot({
|
|||
showStatus &&
|
||||
prDisplay !== null &&
|
||||
QUIET_REVIEW_REPLACEABLE_STATUSES.has(status)
|
||||
const canShowBranchStatus =
|
||||
newCardStyle &&
|
||||
showStatus &&
|
||||
hasBranchIdentity &&
|
||||
prDisplay === null &&
|
||||
QUIET_REVIEW_REPLACEABLE_STATUSES.has(status)
|
||||
const passiveStatusLabel =
|
||||
canShowReviewStatus && prDisplay ? getReviewStatusTooltip(prDisplay) : statusLabel
|
||||
canShowReviewStatus && prDisplay
|
||||
? getReviewStatusTooltip(prDisplay)
|
||||
: canShowBranchStatus
|
||||
? BRANCH_STATUS_LABEL
|
||||
: statusLabel
|
||||
const reviewStatusIconClassName = 'size-4'
|
||||
const branchStatusIcon = <GitBranch className={branchStatusIconClassName} aria-hidden="true" />
|
||||
const passiveStatus =
|
||||
canShowReviewStatus && prDisplay ? (
|
||||
<Tooltip>
|
||||
|
|
@ -82,6 +99,18 @@ export function WorktreeCardStatusSlot({
|
|||
<span>{passiveStatusLabel}</span>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
) : canShowBranchStatus ? (
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<span className={cn('inline-flex size-5 items-center justify-center p-0.5', className)}>
|
||||
{branchStatusIcon}
|
||||
<span className="sr-only">{passiveStatusLabel}</span>
|
||||
</span>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side="right" sideOffset={8}>
|
||||
<span>{passiveStatusLabel}</span>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
) : newCardStyle && showStatus ? (
|
||||
<>
|
||||
<span className={cn('inline-flex size-5 items-center justify-center', className)}>
|
||||
|
|
@ -106,7 +135,7 @@ export function WorktreeCardStatusSlot({
|
|||
|
||||
const actionLabel = isUnread ? 'Mark as read' : 'Mark as unread'
|
||||
const tooltip =
|
||||
showStatus && (!isUnread || (newCardStyle && canShowReviewStatus && prDisplay))
|
||||
showStatus && (!isUnread || (newCardStyle && (canShowBranchStatus || canShowReviewStatus)))
|
||||
? `${passiveStatusLabel} · ${unreadTooltip}`
|
||||
: unreadTooltip
|
||||
|
||||
|
|
@ -135,6 +164,13 @@ export function WorktreeCardStatusSlot({
|
|||
</span>
|
||||
<FilledBellIcon className="absolute -right-1 -top-1 size-[13px] text-amber-500 drop-shadow-sm" />
|
||||
</>
|
||||
) : isUnread && showStatus && canShowBranchStatus ? (
|
||||
<>
|
||||
<span className="inline-flex size-5 items-center justify-center p-0.5">
|
||||
{branchStatusIcon}
|
||||
</span>
|
||||
<FilledBellIcon className="absolute -right-1 -top-1 size-[13px] text-amber-500 drop-shadow-sm" />
|
||||
</>
|
||||
) : isUnread ? (
|
||||
<FilledBellIcon className="size-[13px] text-amber-500 drop-shadow-sm" />
|
||||
) : showStatus && canShowReviewStatus && prDisplay ? (
|
||||
|
|
@ -144,6 +180,13 @@ export function WorktreeCardStatusSlot({
|
|||
</span>
|
||||
<Bell className="absolute size-3 text-muted-foreground/40 opacity-0 transition-opacity group-hover/unread:opacity-100 group-focus-within/unread:opacity-100" />
|
||||
</>
|
||||
) : showStatus && canShowBranchStatus ? (
|
||||
<>
|
||||
<span className="inline-flex size-5 items-center justify-center p-0.5 transition-opacity group-hover/unread:opacity-0 group-focus-within/unread:opacity-0">
|
||||
{branchStatusIcon}
|
||||
</span>
|
||||
<Bell className="absolute size-3 text-muted-foreground/40 opacity-0 transition-opacity group-hover/unread:opacity-100 group-focus-within/unread:opacity-100" />
|
||||
</>
|
||||
) : showStatus ? (
|
||||
<>
|
||||
<StatusIndicator
|
||||
|
|
|
|||
|
|
@ -3667,6 +3667,7 @@
|
|||
"1a0eec0d35": "Status",
|
||||
"b5536d5a88": "Tasks",
|
||||
"8d62c68b35": "Notes",
|
||||
"automation": "Automation",
|
||||
"2d74665a56": "Ports",
|
||||
"65a9820bd1": "Agent statuses",
|
||||
"219ebf1961": "Branch name"
|
||||
|
|
|
|||
|
|
@ -3653,6 +3653,7 @@
|
|||
"1a0eec0d35": "Estado",
|
||||
"b5536d5a88": "Tareas",
|
||||
"8d62c68b35": "Notas",
|
||||
"automation": "Automation",
|
||||
"2d74665a56": "Puertos",
|
||||
"65a9820bd1": "Estados del agente",
|
||||
"219ebf1961": "Nombre de rama"
|
||||
|
|
|
|||
|
|
@ -3636,7 +3636,8 @@
|
|||
"8d62c68b35": "ノート",
|
||||
"2d74665a56": "ポート",
|
||||
"65a9820bd1": "Agent ステータス",
|
||||
"219ebf1961": "ブランチ名"
|
||||
"219ebf1961": "ブランチ名",
|
||||
"automation": "Automation"
|
||||
},
|
||||
"SshDisconnectedDialog": {
|
||||
"ca4a7892af": "接続中...",
|
||||
|
|
|
|||
|
|
@ -3636,7 +3636,8 @@
|
|||
"8d62c68b35": "메모",
|
||||
"2d74665a56": "포트",
|
||||
"65a9820bd1": "Agent 상태",
|
||||
"219ebf1961": "브랜치 이름"
|
||||
"219ebf1961": "브랜치 이름",
|
||||
"automation": "Automation"
|
||||
},
|
||||
"SshDisconnectedDialog": {
|
||||
"ca4a7892af": "연결 중...",
|
||||
|
|
|
|||
|
|
@ -3636,7 +3636,8 @@
|
|||
"8d62c68b35": "笔记",
|
||||
"2d74665a56": "端口",
|
||||
"65a9820bd1": "Agent 状态",
|
||||
"219ebf1961": "分支名称"
|
||||
"219ebf1961": "分支名称",
|
||||
"automation": "Automation"
|
||||
},
|
||||
"SshDisconnectedDialog": {
|
||||
"ca4a7892af": "正在连接...",
|
||||
|
|
|
|||
Loading…
Reference in New Issue