fix(source-control): compact detached head identity

This commit is contained in:
Brennan Benson
2026-07-21 17:54:31 -07:00
parent 1f6910cf90
commit 2b2e756ab0
4 changed files with 54 additions and 20 deletions
@@ -27,6 +27,7 @@ export function DetachedHeadBadge({
<TooltipTrigger asChild>
<Badge
variant="outline"
aria-label={display.tooltip}
className={cn(
'h-[18px] shrink-0 gap-1 rounded px-1.5 text-[10px] font-medium leading-none',
'border-[color:color-mix(in_srgb,var(--git-decoration-modified)_30%,transparent)] bg-[color:color-mix(in_srgb,var(--git-decoration-modified)_8%,transparent)] text-[color:var(--git-decoration-modified)]',
@@ -34,7 +35,7 @@ export function DetachedHeadBadge({
)}
>
<GitCommitHorizontal className="size-2.5" />
{visibleLabel}
<span className="min-w-0 truncate">{visibleLabel}</span>
</Badge>
</TooltipTrigger>
<TooltipContent side={side} sideOffset={8}>
@@ -45,7 +45,6 @@ import { WORKSPACE_FILE_PATH_MIME } from '@/lib/workspace-file-drag'
import { isFolderRepo } from '../../../../shared/repo-kind'
import { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider } from '@/components/ui/tooltip'
import { Button } from '@/components/ui/button'
import { DetachedHeadBadge } from '@/components/DetachedHeadBadge'
import {
DropdownMenu,
DropdownMenuContent,
@@ -808,7 +807,6 @@ function SourceControlInner(): React.JSX.Element {
const activeRepoConnectionId = activeRepo?.connectionId ?? null
const activeRepoExecutionHostId = activeRepo?.executionHostId ?? null
const gitIdentityDisplay = activeWorktree ? getWorktreeGitIdentityDisplay(activeWorktree) : null
const detachedHeadDisplay = gitIdentityDisplay?.kind === 'detached' ? gitIdentityDisplay : null
const branchName = gitIdentityDisplay?.kind === 'branch' ? gitIdentityDisplay.branchName : ''
const entries = useAppStore((s) =>
activeWorktreeId
@@ -5446,7 +5444,7 @@ function SourceControlInner(): React.JSX.Element {
<>
<div ref={setSourceControlRoot} className="relative flex h-full flex-col overflow-hidden">
<SourceControlHeaderToolbar
branchName={branchName}
gitIdentityDisplay={gitIdentityDisplay}
filterQuery={filterQuery}
filterExpanded={filterExpanded}
onFilterQueryChange={setFilterQuery}
@@ -5469,12 +5467,6 @@ function SourceControlInner(): React.JSX.Element {
manualReviewUrl={manualReviewUrl}
/>
{detachedHeadDisplay && (
<div className="border-b border-border px-3 py-2">
<DetachedHeadBadge display={detachedHeadDisplay} side="bottom" />
</div>
)}
{/* Why: hidden when count is 0 — notes are created from the diff view, so an empty Notes shelf here is pure chrome. */}
{activeWorktreeId && worktreePath && diffCommentCount > 0 && (
<div className="border-b border-border">
@@ -1,6 +1,7 @@
import { renderToStaticMarkup } from 'react-dom/server'
import type { ReactNode } from 'react'
import { describe, expect, it, vi } from 'vitest'
import type { WorktreeGitIdentityDisplay } from '@/lib/worktree-git-identity-display'
import { SourceControlHeaderToolbar } from './source-control-header-toolbar'
vi.mock('@/components/ui/tooltip', () => ({
@@ -18,10 +19,15 @@ vi.mock('./source-control-branch-context-row', () => ({
SourceControlBranchContextRow: () => null
}))
function renderToolbar(branchName = 'brennanb2025/source-control-branch-name'): string {
function renderToolbar(
gitIdentityDisplay: WorktreeGitIdentityDisplay | null = {
kind: 'branch',
branchName: 'brennanb2025/source-control-branch-name'
}
): string {
return renderToStaticMarkup(
<SourceControlHeaderToolbar
branchName={branchName}
gitIdentityDisplay={gitIdentityDisplay}
filterQuery=""
filterExpanded={false}
onFilterQueryChange={vi.fn()}
@@ -56,10 +62,30 @@ describe('SourceControlHeaderToolbar', () => {
expect(markup).toContain('min-w-0 truncate')
})
it('does not announce a branch while HEAD is detached', () => {
const markup = renderToolbar('')
it('renders detached HEAD in the same identity slot', () => {
const markup = renderToolbar({
kind: 'detached',
shortHead: '8cec248',
sidebarLabel: 'Detached HEAD @ 8cec248',
sourceControlLabel: 'Detached HEAD · 8cec248',
tooltip: 'Detached HEAD at 8cec248. You are viewing a commit, not a branch.'
})
const identityIndex = markup.indexOf('Detached HEAD · 8cec248')
const filterIndex = markup.indexOf('data-testid="source-control-filter-toggle"')
expect(markup).not.toContain('aria-label="Current branch:')
expect(markup).not.toContain('lucide-git-branch')
expect(identityIndex).toBeGreaterThan(-1)
expect(filterIndex).toBeGreaterThan(identityIndex)
expect(markup).toContain(
'aria-label="Detached HEAD at 8cec248. You are viewing a commit, not a branch."'
)
expect(markup).toContain('lucide-git-commit-horizontal')
})
it('keeps the identity slot empty until git identity is known', () => {
const markup = renderToolbar(null)
expect(markup).not.toContain('aria-label="Current branch:')
expect(markup).not.toContain('Detached HEAD')
})
})
@@ -10,6 +10,8 @@ import { Button } from '@/components/ui/button'
import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip'
import { cn } from '@/lib/utils'
import { translate } from '@/i18n/i18n'
import { DetachedHeadBadge } from '@/components/DetachedHeadBadge'
import type { WorktreeGitIdentityDisplay } from '@/lib/worktree-git-identity-display'
import {
shouldShowSourceControlBranchContextRow,
SourceControlBranchContextRow
@@ -17,7 +19,7 @@ import {
import { SourceControlHeaderOverflowMenu } from './source-control-header-overflow-menu'
type SourceControlHeaderToolbarProps = {
branchName: string
gitIdentityDisplay: WorktreeGitIdentityDisplay | null
filterQuery: string
filterExpanded: boolean
onFilterQueryChange: (value: string) => void
@@ -40,7 +42,20 @@ type SourceControlHeaderToolbarProps = {
manualReviewUrl?: string | null
}
function SourceControlBranchHeaderLabel({ branchName }: { branchName: string }): React.JSX.Element {
function SourceControlGitIdentityLabel({
display
}: {
display: WorktreeGitIdentityDisplay
}): React.JSX.Element {
if (display.kind === 'detached') {
return (
<span className="flex min-w-0 flex-1 items-center">
<DetachedHeadBadge display={display} side="bottom" className="min-w-0 max-w-full shrink" />
</span>
)
}
const branchName = display.branchName
const label = translate(
'auto.components.right.sidebar.SourceControl.a4e93c21d7',
'Current branch: {{value0}}',
@@ -121,7 +136,7 @@ function renderOverflowMenu(
}
export function SourceControlHeaderToolbar({
branchName,
gitIdentityDisplay,
filterQuery,
filterExpanded,
onFilterQueryChange,
@@ -192,8 +207,8 @@ export function SourceControlHeaderToolbar({
>
{showCollapsedToolbar ? (
<>
{branchName ? (
<SourceControlBranchHeaderLabel branchName={branchName} />
{gitIdentityDisplay ? (
<SourceControlGitIdentityLabel display={gitIdentityDisplay} />
) : (
<span className="min-w-0 flex-1" aria-hidden="true" />
)}