Hide branch names behind workspace card property
This commit is contained in:
parent
b8a7adf8ed
commit
3dd281eb8e
|
|
@ -2111,6 +2111,7 @@ describe('Store', () => {
|
|||
expect(store.getUI().worktreeCardProperties).toContain('inline-agents')
|
||||
expect(store.getUI().worktreeCardProperties).toContain('linear-issue')
|
||||
expect(store.getUI().worktreeCardProperties).toContain('ports')
|
||||
expect(store.getUI().worktreeCardProperties).not.toContain('branch')
|
||||
expect(store.getUI()._inlineAgentsDefaultedForExperiment).toBe(true)
|
||||
expect(store.getUI()._inlineAgentsDefaultedForAllUsers).toBe(true)
|
||||
expect(store.getUI()._expandedWorktreeCardPropertiesDefaulted).toBe(true)
|
||||
|
|
|
|||
|
|
@ -136,7 +136,7 @@ describe('client UI RPC methods', () => {
|
|||
it('accepts persisted literal UI arrays and nested UI state', async () => {
|
||||
const updated: PersistedUIState = {
|
||||
...getDefaultUIState(),
|
||||
worktreeCardProperties: ['status', 'inline-agents'],
|
||||
worktreeCardProperties: ['status', 'branch', 'inline-agents'],
|
||||
statusBarItems: ['codex'],
|
||||
taskResumeState: {
|
||||
githubMode: 'items',
|
||||
|
|
@ -163,7 +163,7 @@ describe('client UI RPC methods', () => {
|
|||
const dispatcher = new RpcDispatcher({ runtime, methods: CLIENT_UI_METHODS })
|
||||
|
||||
const payload = {
|
||||
worktreeCardProperties: ['status', 'inline-agents'],
|
||||
worktreeCardProperties: ['status', 'branch', 'inline-agents'],
|
||||
statusBarItems: ['codex'],
|
||||
taskResumeState: {
|
||||
githubMode: 'items',
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ const UnknownRecordArray = z.array(UnknownRecord)
|
|||
const WorktreeCardProperty = z.enum([
|
||||
'status',
|
||||
'unread',
|
||||
'branch',
|
||||
'ci',
|
||||
'issue',
|
||||
'linear-issue',
|
||||
|
|
|
|||
|
|
@ -35,6 +35,7 @@ const GROUP_BY_OPTIONS = [
|
|||
] as const
|
||||
|
||||
const PROPERTY_OPTIONS: { id: WorktreeCardProperty; label: string }[] = [
|
||||
{ id: 'branch', label: 'Branch name' },
|
||||
{ id: 'issue', label: 'GitHub ticket' },
|
||||
{ id: 'linear-issue', label: 'Linear issue' },
|
||||
{ id: 'pr', label: 'PR/MR link' },
|
||||
|
|
|
|||
|
|
@ -125,6 +125,41 @@ describe('WorktreeCard quick actions', () => {
|
|||
expect(markup).toContain('data-workspace-board-preserve-open=""')
|
||||
})
|
||||
|
||||
it('hides the branch name from workspace card metadata by default', () => {
|
||||
const markup = renderToStaticMarkup(
|
||||
<WorktreeCard
|
||||
worktree={makeWorktree({
|
||||
displayName: 'Visible workspace',
|
||||
branch: 'refs/heads/feature/secret-ref'
|
||||
})}
|
||||
repo={makeRepo()}
|
||||
isActive={false}
|
||||
/>
|
||||
)
|
||||
|
||||
expect(markup).toContain('Visible workspace')
|
||||
expect(markup).not.toContain('feature/secret-ref')
|
||||
expect(markup).not.toContain('secret-ref')
|
||||
})
|
||||
|
||||
it('renders the branch name when the Branch name property is enabled', () => {
|
||||
worktreeCardProperties = ['status', 'unread', 'branch']
|
||||
|
||||
const markup = renderToStaticMarkup(
|
||||
<WorktreeCard
|
||||
worktree={makeWorktree({
|
||||
displayName: 'Visible workspace',
|
||||
branch: 'refs/heads/feature/show-ref'
|
||||
})}
|
||||
repo={makeRepo()}
|
||||
isActive={false}
|
||||
/>
|
||||
)
|
||||
|
||||
expect(markup).toContain('Visible workspace')
|
||||
expect(markup).toContain('feature/show-ref')
|
||||
})
|
||||
|
||||
it('shows delete as the top-right quick action for an inactive workspace', () => {
|
||||
const markup = renderToStaticMarkup(
|
||||
<WorktreeCard worktree={makeWorktree()} repo={makeRepo()} isActive={false} />
|
||||
|
|
|
|||
|
|
@ -254,6 +254,7 @@ const WorktreeCard = React.memo(function WorktreeCard({
|
|||
const showLinearIssue = cardProps.includes('linear-issue')
|
||||
const showComment = cardProps.includes('comment')
|
||||
const showPorts = cardProps.includes('ports')
|
||||
const showBranch = cardProps.includes('branch')
|
||||
|
||||
// Skip hosted-review fetches when the corresponding card sections are hidden.
|
||||
// This preference is purely presentational, so background refreshes would
|
||||
|
|
@ -415,8 +416,7 @@ const WorktreeCard = React.memo(function WorktreeCard({
|
|||
)
|
||||
// Why: deleting the active/current workspace or one with live activity is a
|
||||
// disruptive hover action; keep the quick action delete-only and passive.
|
||||
const showDeleteQuickAction =
|
||||
!isCurrentWorktree && !hasActiveActivity && !worktree.isMainWorktree
|
||||
const showDeleteQuickAction = !isCurrentWorktree && !hasActiveActivity && !worktree.isMainWorktree
|
||||
const handleWorkspaceQuickAction = useCallback(
|
||||
(event: React.MouseEvent<HTMLButtonElement>) => {
|
||||
event.preventDefault()
|
||||
|
|
@ -624,10 +624,9 @@ const WorktreeCard = React.memo(function WorktreeCard({
|
|||
)}
|
||||
|
||||
{/* Why: weight alone carries the unread signal; color stays
|
||||
at text-foreground in both states so the title keeps
|
||||
hierarchy against the muted branch row below (muting the
|
||||
title as well flattened the card — same reasoning as the
|
||||
repo chip comment below). */}
|
||||
at text-foreground in both states so the title remains
|
||||
the card's primary scan target even when optional branch
|
||||
metadata is hidden. */}
|
||||
<div
|
||||
className={cn(
|
||||
'text-[12px] truncate leading-tight text-foreground',
|
||||
|
|
@ -643,8 +642,8 @@ const WorktreeCard = React.memo(function WorktreeCard({
|
|||
|
||||
{/* Why: the primary worktree (the original clone directory) cannot be
|
||||
deleted via `git worktree remove`. Placing this badge next to the
|
||||
name makes it immediately visible and avoids confusion with the
|
||||
branch name "main" shown below. */}
|
||||
name keeps that constraint visible even when branch metadata is
|
||||
hidden by the user's card-property choice. */}
|
||||
{worktree.isMainWorktree && !isFolder && (
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
|
|
@ -735,11 +734,11 @@ const WorktreeCard = React.memo(function WorktreeCard({
|
|||
>
|
||||
{repo ? getRepoKindLabel(repo) : 'Folder'}
|
||||
</Badge>
|
||||
) : (
|
||||
) : showBranch && branch ? (
|
||||
<span className="min-w-0 text-[11px] text-muted-foreground truncate leading-none">
|
||||
{branch}
|
||||
</span>
|
||||
)}
|
||||
) : null}
|
||||
|
||||
{/* Why: the conflict operation (merge/rebase/cherry-pick) is the
|
||||
only signal that the worktree is in an incomplete operation state.
|
||||
|
|
|
|||
|
|
@ -249,5 +249,23 @@ describe('WorktreeList lineage child card renderer', () => {
|
|||
expect(agentRowIndex).toBeGreaterThan(childStart)
|
||||
expect(childToggleIndex).toBeGreaterThan(childStart)
|
||||
expect(agentRowIndex).toBeLessThan(childToggleIndex)
|
||||
expect(markup).not.toContain('child-branch')
|
||||
expect(markup).not.toContain('grandchild-branch')
|
||||
})
|
||||
|
||||
it('renders nested lineage branch labels when the Branch name property is enabled', async () => {
|
||||
setLineageFixtureState()
|
||||
mockStore.state.worktreeCardProperties = ['status', 'inline-agents', 'branch']
|
||||
const { default: WorktreeList } = await import('./WorktreeList')
|
||||
|
||||
const markup = renderToStaticMarkup(
|
||||
React.createElement(WorktreeList, {
|
||||
scrollOffsetRef: { current: 0 },
|
||||
scrollAnchorRef: { current: null }
|
||||
})
|
||||
)
|
||||
|
||||
expect(markup).toContain('child-branch')
|
||||
expect(markup).toContain('grandchild-branch')
|
||||
})
|
||||
})
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@ import {
|
|||
useWorktreeMap
|
||||
} from '@/store/selectors'
|
||||
import WorktreeCard from './WorktreeCard'
|
||||
import { branchDisplayName } from './WorktreeCardHelpers'
|
||||
import WorktreeCardAgents from './WorktreeCardAgents'
|
||||
import { SshDisconnectedDialog } from './SshDisconnectedDialog'
|
||||
import { WorktreeActivityStatusIndicator } from './WorktreeActivityStatusIndicator'
|
||||
|
|
@ -125,7 +126,6 @@ import {
|
|||
pruneWorktreeSelection,
|
||||
updateWorktreeSelection
|
||||
} from './worktree-multi-selection'
|
||||
import { branchDisplayName } from './WorktreeCardHelpers'
|
||||
import { callRuntimeRpc, getActiveRuntimeTarget } from '@/runtime/runtime-rpc-client'
|
||||
import { getRepoHeaderCreateState } from './repo-header-create-state'
|
||||
import type { PendingSidebarWorktreeReveal } from '@/store/slices/ui'
|
||||
|
|
@ -2162,6 +2162,10 @@ const VirtualizedWorktreeViewport = React.memo(function VirtualizedWorktreeViewp
|
|||
|
||||
const renderLineageChildCard = (child: WorktreeItemRow) => {
|
||||
const isActive = activeWorktreeId === child.worktree.id
|
||||
const childRepo = child.repo
|
||||
const showChildRepoBadge = childRepo !== undefined && groupBy !== 'repo'
|
||||
const showChildBranch =
|
||||
cardProps.includes('branch') && child.worktree.branch.trim() !== ''
|
||||
const handleClick = (event: React.MouseEvent<HTMLDivElement>) => {
|
||||
event.preventDefault()
|
||||
event.stopPropagation()
|
||||
|
|
@ -2211,22 +2215,26 @@ const VirtualizedWorktreeViewport = React.memo(function VirtualizedWorktreeViewp
|
|||
<div className="truncate text-[12px] leading-tight text-foreground">
|
||||
{child.worktree.displayName}
|
||||
</div>
|
||||
<div className="mt-1 flex min-w-0 items-center gap-1.5">
|
||||
{child.repo && groupBy !== 'repo' ? (
|
||||
<span className="flex h-[16px] shrink-0 items-center gap-1.5 rounded-[4px] border border-border bg-accent px-1.5 text-[10px] font-semibold leading-none text-foreground dark:bg-accent/50 dark:border-border/60">
|
||||
<span
|
||||
className="size-1.5 rounded-full"
|
||||
style={{ backgroundColor: child.repo.badgeColor }}
|
||||
/>
|
||||
<span className="max-w-[6rem] truncate lowercase">
|
||||
{child.repo.displayName}
|
||||
{showChildRepoBadge || showChildBranch ? (
|
||||
<div className="mt-1 flex min-w-0 items-center gap-1.5">
|
||||
{showChildRepoBadge && childRepo ? (
|
||||
<span className="flex h-[16px] shrink-0 items-center gap-1.5 rounded-[4px] border border-border bg-accent px-1.5 text-[10px] font-semibold leading-none text-foreground dark:bg-accent/50 dark:border-border/60">
|
||||
<span
|
||||
className="size-1.5 rounded-full"
|
||||
style={{ backgroundColor: childRepo.badgeColor }}
|
||||
/>
|
||||
<span className="max-w-[6rem] truncate lowercase">
|
||||
{childRepo.displayName}
|
||||
</span>
|
||||
</span>
|
||||
</span>
|
||||
) : null}
|
||||
<span className="truncate text-[10.5px] leading-none text-muted-foreground">
|
||||
{branchDisplayName(child.worktree.branch)}
|
||||
</span>
|
||||
</div>
|
||||
) : null}
|
||||
{showChildBranch ? (
|
||||
<span className="truncate text-[10.5px] leading-none text-muted-foreground">
|
||||
{branchDisplayName(child.worktree.branch)}
|
||||
</span>
|
||||
) : null}
|
||||
</div>
|
||||
) : null}
|
||||
{child.worktree.linkedIssue || child.worktree.comment ? (
|
||||
<div className="mt-1.5 truncate text-[10.5px] leading-tight text-muted-foreground">
|
||||
{child.worktree.linkedIssue ? (
|
||||
|
|
|
|||
|
|
@ -547,6 +547,19 @@ describe('createUISlice hydratePersistedUI', () => {
|
|||
expect(store.getState().worktreeCardProperties).toEqual(expected)
|
||||
expect(setUI).toHaveBeenCalledWith({ worktreeCardProperties: expected })
|
||||
})
|
||||
|
||||
it('persists Branch name as an opt-in card property', () => {
|
||||
const setUI = vi.fn().mockResolvedValue(undefined)
|
||||
vi.stubGlobal('window', { api: { ui: { set: setUI } } })
|
||||
const store = createUIStore()
|
||||
|
||||
store.getState().toggleWorktreeCardProperty('branch')
|
||||
|
||||
expect(store.getState().worktreeCardProperties).toContain('branch')
|
||||
expect(setUI).toHaveBeenCalledWith({
|
||||
worktreeCardProperties: expect.arrayContaining(['status', 'unread', 'branch'])
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('createUISlice settings navigation', () => {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,9 @@
|
|||
import { describe, expect, it } from 'vitest'
|
||||
import { getDefaultPrimarySelectionMiddleClickPaste, getDefaultSettings } from './constants'
|
||||
import {
|
||||
getDefaultPrimarySelectionMiddleClickPaste,
|
||||
getDefaultSettings,
|
||||
getDefaultUIState
|
||||
} from './constants'
|
||||
|
||||
describe('getDefaultSettings', () => {
|
||||
it('enables gitignored file decorations by default', () => {
|
||||
|
|
@ -36,3 +40,9 @@ describe('getDefaultPrimarySelectionMiddleClickPaste', () => {
|
|||
expect(getDefaultPrimarySelectionMiddleClickPaste('win32')).toBe(false)
|
||||
})
|
||||
})
|
||||
|
||||
describe('getDefaultUIState', () => {
|
||||
it('keeps branch names hidden from workspace cards by default', () => {
|
||||
expect(getDefaultUIState().worktreeCardProperties).not.toContain('branch')
|
||||
})
|
||||
})
|
||||
|
|
|
|||
|
|
@ -2025,6 +2025,9 @@ export type NotificationPermissionStatusResult = {
|
|||
export type WorktreeCardProperty =
|
||||
| 'status'
|
||||
| 'unread'
|
||||
// Git branch metadata shown on workspace cards. Hidden by default because
|
||||
// workspace names already carry the primary identity for most lists.
|
||||
| 'branch'
|
||||
// Legacy persisted preference. CI status is now represented by linked PR metadata.
|
||||
| 'ci'
|
||||
// GitHub issue metadata shown on workspace cards.
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ export const DEFAULT_WORKTREE_CARD_PROPERTIES: WorktreeCardProperty[] = [
|
|||
const WORKTREE_CARD_PROPERTY_ORDER: WorktreeCardProperty[] = [
|
||||
'status',
|
||||
'unread',
|
||||
'branch',
|
||||
'ci',
|
||||
'issue',
|
||||
'linear-issue',
|
||||
|
|
|
|||
Loading…
Reference in New Issue