Use square repo badges (#2744)

This commit is contained in:
Neil 2026-05-24 00:25:27 -07:00 committed by GitHub
parent 8b9d1d561d
commit 476f367c9d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
20 changed files with 92 additions and 108 deletions

View File

@ -72,7 +72,7 @@ import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip
import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover'
import RepoMultiCombobox from '@/components/ui/repo-multi-combobox'
import TeamMultiCombobox from '@/components/ui/team-multi-combobox'
import RepoDotLabel from '@/components/repo/RepoDotLabel'
import RepoBadgeLabel from '@/components/repo/RepoBadgeLabel'
import IssueSourceIndicator, { sameGitHubOwnerRepo } from '@/components/github/IssueSourceIndicator'
import IssueSourceSelector, { issueSourceChipClass } from '@/components/github/IssueSourceSelector'
import { reconcileLinearTeamSelection } from '@/components/task-page-linear-team-selection'
@ -4447,7 +4447,7 @@ export default function TaskPage(): React.JSX.Element {
// Why: unify feature 1 (indicator) and feature 2 (selector)
// into a single chip per repo. Rendering both separately
// produced visually redundant output — two local-repo
// dot-labels, duplicate slugs. The selector's active pill
// badge labels, duplicate slugs. The selector's active pill
// + tooltip already announce the source, so the "Issues
// from {slug}" chip is only shown when the selector does
// not render (no upstream remote — nothing to toggle).
@ -4461,7 +4461,7 @@ export default function TaskPage(): React.JSX.Element {
<div className="mt-2 flex flex-wrap items-center gap-2">
{rows.map((s) => {
const repo = selectedRepos.find((r) => r.id === s.repoId)
const showDotLabel = selectedRepos.length > 1 && repo
const showRepoBadgeLabel = selectedRepos.length > 1 && repo
const selectorRenderable = hasUpstreamCandidateDivergence(s)
// Why: the static indicator has its own wrapping
// chip styles, so we render it standalone and don't
@ -4474,7 +4474,7 @@ export default function TaskPage(): React.JSX.Element {
issues={s.sources.issues}
prs={s.sources.prs}
localRepo={
showDotLabel && repo
showRepoBadgeLabel && repo
? { displayName: repo.displayName, color: repo.badgeColor }
: undefined
}
@ -4492,11 +4492,11 @@ export default function TaskPage(): React.JSX.Element {
// uses `inline-flex`, so the visual rendering is identical.
return (
<div key={s.repoId} className={issueSourceChipClass}>
{showDotLabel ? (
<RepoDotLabel
{showRepoBadgeLabel ? (
<RepoBadgeLabel
name={repo.displayName}
color={repo.badgeColor}
dotClassName="size-1.5"
badgeClassName="size-1.5"
className="text-[10px] text-muted-foreground"
/>
) : null}
@ -5008,10 +5008,10 @@ export default function TaskPage(): React.JSX.Element {
{selectedRepos.length > 1 && itemRepo ? (
// Why: disambiguate rows when multiple repos are in
// the merged list — a single-repo view doesn't need it.
<RepoDotLabel
<RepoBadgeLabel
name={itemRepo.displayName}
color={itemRepo.badgeColor}
dotClassName="size-1.5"
badgeClassName="size-1.5"
className="shrink-0 text-[11px] text-muted-foreground"
/>
) : null}
@ -6002,7 +6002,7 @@ export default function TaskPage(): React.JSX.Element {
<SelectContent>
{selectedRepos.map((r) => (
<SelectItem key={r.id} value={r.id}>
<RepoDotLabel name={r.displayName} color={r.badgeColor} />
<RepoBadgeLabel name={r.displayName} color={r.badgeColor} />
</SelectItem>
))}
</SelectContent>

View File

@ -46,6 +46,7 @@ import {
ORCA_BROWSER_FOCUS_REQUEST_EVENT,
queueBrowserFocusRequest
} from '@/components/browser-pane/browser-focus'
import { RepoBadgeMark } from '@/components/repo/RepoBadgeLabel'
import type { BrowserPage, BrowserWorkspace, Worktree } from '../../../shared/types'
import { isGitRepoKind } from '../../../shared/repo-kind'
@ -1043,15 +1044,7 @@ export default function WorktreeJumpPalette(): React.JSX.Element | null {
<div className="flex shrink-0 flex-col items-end gap-1.5">
{repoName && (
<span className="inline-flex max-w-[180px] items-center gap-1.5 rounded-md border border-border bg-muted px-2 py-1 text-[11px] font-semibold leading-none text-foreground">
<span
aria-hidden="true"
className="size-1.5 shrink-0 rounded-full"
style={
repo?.badgeColor
? { backgroundColor: repo.badgeColor }
: undefined
}
/>
<RepoBadgeMark color={repo?.badgeColor} />
<span className="truncate">
{entry.match.repoRange ? (
<HighlightedText
@ -1125,15 +1118,7 @@ export default function WorktreeJumpPalette(): React.JSX.Element | null {
<div className="flex shrink-0 flex-col items-end gap-1.5">
{browserRepoName && (
<span className="inline-flex max-w-[180px] items-center gap-1.5 rounded-md border border-border bg-muted px-2 py-1 text-[11px] font-semibold leading-none text-foreground">
<span
aria-hidden="true"
className="size-1.5 shrink-0 rounded-full"
style={
browserRepo?.badgeColor
? { backgroundColor: browserRepo.badgeColor }
: undefined
}
/>
<RepoBadgeMark color={browserRepo?.badgeColor} />
<span className="truncate">
<HighlightedText
text={browserRepoName}

View File

@ -27,6 +27,7 @@ import type { RetainedAgentEntry } from '@/store/slices/agent-status'
import { getRepoMapFromState, getWorktreeMapFromState } from '@/store/selectors'
import { useSidebarResize } from '@/hooks/useSidebarResize'
import { Button } from '@/components/ui/button'
import { RepoBadgeMark } from '@/components/repo/RepoBadgeLabel'
import {
DropdownMenu,
DropdownMenuCheckboxItem,
@ -868,7 +869,7 @@ function EventRepoBadge({ repo }: { repo: Repo | null }): React.JSX.Element | nu
}
return (
<div className="flex min-w-0 shrink-0 items-center gap-1.5 rounded-[4px] border border-border bg-accent px-1.5 py-0.5 dark:border-border/60 dark:bg-accent/50">
<div className="size-1.5 rounded-full" style={{ backgroundColor: repo.badgeColor }} />
<RepoBadgeMark color={repo.badgeColor} />
<span className="max-w-[6rem] truncate text-[10px] font-semibold leading-none text-foreground lowercase">
{repo.displayName}
</span>

View File

@ -36,7 +36,7 @@ import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'
import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip'
import { useAppStore } from '@/store'
import { cn } from '@/lib/utils'
import RepoDotLabel from '@/components/repo/RepoDotLabel'
import RepoBadgeLabel from '@/components/repo/RepoBadgeLabel'
import { AGENT_CATALOG } from '@/lib/agent-catalog'
import { useRepoMap, useWorktreeMap } from '@/store/selectors'
import { activateAndRevealWorktree } from '@/lib/worktree-activation'
@ -1496,10 +1496,10 @@ export default function AutomationsPage(): React.JSX.Element {
</span>
<span className="mt-1 flex min-w-0 items-center gap-1.5 text-xs text-muted-foreground">
{automationRepo ? (
<RepoDotLabel
<RepoBadgeLabel
name={automationRepo.displayName}
color={automationRepo.badgeColor}
dotClassName="size-1.5"
badgeClassName="size-1.5"
/>
) : (
<span>Unknown project</span>

View File

@ -1,6 +1,6 @@
import React from 'react'
import type { GitHubOwnerRepo } from '../../../../shared/types'
import RepoDotLabel from '@/components/repo/RepoDotLabel'
import RepoBadgeLabel from '@/components/repo/RepoBadgeLabel'
import { cn } from '@/lib/utils'
export type IssueSourceIndicatorProps = {
@ -84,13 +84,13 @@ export default function IssueSourceIndicator({
>
{localRepo ? (
// Why: when multiple repos are selected, a bare "Issues from X/Y" chip
// doesn't tell the user which local repo it describes. The dot-label
// doesn't tell the user which local repo it describes. The badge label
// prefix pins the chip to the same visual identity used elsewhere in
// the Tasks view (row disambiguator, composer target dropdown).
<RepoDotLabel
<RepoBadgeLabel
name={localRepo.displayName}
color={localRepo.color}
dotClassName="size-1.5"
badgeClassName="size-1.5"
className="text-[10px] text-muted-foreground"
/>
) : null}

View File

@ -54,7 +54,7 @@ function segmentClass(state: PillState, disabled: boolean | undefined): string {
}
// Why: exported so the Tasks-header row can wrap the selector (and the
// optional per-repo dot-label prefix) in the same pill shape used by the
// optional per-repo badge label prefix) in the same pill shape used by the
// static `IssueSourceIndicator`. Keeping the styling here means the chip
// and its segments stay visually consistent.
export const issueSourceChipClass =

View File

@ -0,0 +1,33 @@
import type { CSSProperties } from 'react'
import { cn } from '@/lib/utils'
type RepoBadgeMarkProps = {
color: string | null | undefined
className?: string
}
export function RepoBadgeMark({ color, className }: RepoBadgeMarkProps) {
const style: CSSProperties | undefined = color ? { backgroundColor: color } : undefined
return (
<span aria-hidden="true" className={cn('block size-1.5 shrink-0', className)} style={style} />
)
}
type RepoBadgeLabelProps = {
name: string
color: string
className?: string
badgeClassName?: string
}
function RepoBadgeLabel({ name, color, className, badgeClassName }: RepoBadgeLabelProps) {
return (
<span className={cn('inline-flex min-w-0 items-center gap-1.5', className)}>
<RepoBadgeMark color={color} className={badgeClassName} />
<span className="truncate">{name}</span>
</span>
)
}
export default RepoBadgeLabel

View File

@ -14,7 +14,7 @@ import { isGitRepoKind } from '../../../../shared/repo-kind'
import { searchRepos } from '@/lib/repo-search'
import { cn } from '@/lib/utils'
import type { Repo } from '../../../../shared/types'
import RepoDotLabel from './RepoDotLabel'
import RepoBadgeLabel from './RepoBadgeLabel'
type RepoComboboxProps = {
repos: Repo[]
@ -174,10 +174,10 @@ export default function RepoCombobox({
>
{selectedRepo ? (
<span className="inline-flex min-w-0 items-center gap-1.5">
<RepoDotLabel
<RepoBadgeLabel
name={selectedRepo.displayName}
color={selectedRepo.badgeColor}
dotClassName="size-1.5"
badgeClassName="size-1.5"
/>
{selectedRepo.connectionId && (
<span className="shrink-0 inline-flex items-center gap-0.5 rounded bg-muted px-1 py-0.5 text-[9px] font-medium leading-none text-muted-foreground">
@ -221,7 +221,7 @@ export default function RepoCombobox({
/>
<div className="min-w-0 flex-1">
<span className="inline-flex items-center gap-1.5">
<RepoDotLabel
<RepoBadgeLabel
name={repo.displayName}
color={repo.badgeColor}
className="max-w-full"

View File

@ -1,23 +0,0 @@
import type { CSSProperties } from 'react'
import { cn } from '@/lib/utils'
type RepoDotLabelProps = {
name: string
color: string
className?: string
dotClassName?: string
}
function RepoDotLabel({ name, color, className, dotClassName }: RepoDotLabelProps) {
return (
<span className={cn('inline-flex min-w-0 items-center gap-1.5', className)}>
<span
className={cn('size-1.5 shrink-0 rounded-full', dotClassName)}
style={{ backgroundColor: color } as CSSProperties}
/>
<span className="truncate">{name}</span>
</span>
)
}
export default RepoDotLabel

View File

@ -17,7 +17,7 @@ import { Button } from '../ui/button'
import { Command, CommandItem, CommandList } from '../ui/command'
import { Label } from '../ui/label'
import { Popover, PopoverContent, PopoverTrigger } from '../ui/popover'
import RepoDotLabel from '../repo/RepoDotLabel'
import RepoBadgeLabel, { RepoBadgeMark } from '../repo/RepoBadgeLabel'
import { cn } from '@/lib/utils'
import { useConfirmationDialog } from '@/components/confirmation-dialog'
@ -270,7 +270,7 @@ export function QuickCommandsPane({
isSelected ? 'opacity-70' : 'opacity-0'
)}
/>
<RepoDotLabel
<RepoBadgeLabel
name={getRepoLabel(repo)}
color={repo.badgeColor}
className="max-w-full"
@ -308,13 +308,7 @@ export function QuickCommandsPane({
<Badge variant="outline" className="max-w-44 gap-1.5">
{scope.type === 'repo' ? (
<>
<span
aria-hidden
className="size-1.5 shrink-0 rounded-full"
style={{
backgroundColor: repoById.get(scope.repoId)?.badgeColor
}}
/>
<RepoBadgeMark color={repoById.get(scope.repoId)?.badgeColor} />
<span className="truncate">{getScopeLabel(scope, repoById)}</span>
</>
) : (

View File

@ -156,7 +156,7 @@ export function RepositoryIconPicker({
type="button"
onClick={() => updateRepo(repo.id, { badgeColor: color })}
className={cn(
'size-7 rounded-full transition-all',
'size-7 rounded-[4px] transition-all',
repo.badgeColor === color
? 'ring-2 ring-foreground ring-offset-2 ring-offset-background'
: 'hover:ring-1 hover:ring-muted-foreground hover:ring-offset-2 hover:ring-offset-background'

View File

@ -2,6 +2,7 @@ import type { RefObject } from 'react'
import { ArrowLeft, Search, Server, type LucideIcon, type LucideProps } from 'lucide-react'
import { useShortcutLabel } from '@/hooks/useShortcutLabel'
import { cn } from '@/lib/utils'
import { RepoBadgeMark } from '../repo/RepoBadgeLabel'
import { Button } from '../ui/button'
import { Input } from '../ui/input'
@ -161,14 +162,12 @@ export function SettingsSidebar({
}
className={navItemClassName(isActive)}
>
<span
<RepoBadgeMark
color={section.badgeColor}
className={cn(
'size-2.5 shrink-0 rounded-full',
'size-2.5',
section.badgeColor ? null : 'bg-muted-foreground'
)}
style={
section.badgeColor ? { backgroundColor: section.badgeColor } : undefined
}
/>
<span className="truncate">{section.title}</span>
{section.isRemote && (

View File

@ -7,6 +7,7 @@ import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip
import { track } from '@/lib/telemetry'
import { cn } from '@/lib/utils'
import { getRepositoryLocalCommandsSectionId } from '@/components/settings/repository-settings-targets'
import { RepoBadgeMark } from '@/components/repo/RepoBadgeLabel'
import {
checkRuntimeHooks,
inspectRuntimeSetupScriptImports,
@ -352,10 +353,7 @@ function SetupScriptPromptCard(): React.JSX.Element | null {
<>
Automate workspace setup for{' '}
<span className="inline-flex items-center gap-1.5 align-baseline px-1.5 py-0.5 rounded-[4px] bg-accent border border-border dark:bg-accent/50 dark:border-border/60">
<span
className="size-1.5 rounded-full"
style={{ backgroundColor: activeRepo.badgeColor }}
/>
<RepoBadgeMark color={activeRepo.badgeColor} />
<span className="text-[10px] font-semibold text-foreground truncate max-w-[8rem] leading-none lowercase">
{activeRepo.displayName}
</span>

View File

@ -16,7 +16,7 @@ import {
DropdownMenuTrigger
} from '@/components/ui/dropdown-menu'
import { Tooltip, TooltipTrigger, TooltipContent } from '@/components/ui/tooltip'
import RepoDotLabel from '@/components/repo/RepoDotLabel'
import RepoBadgeLabel from '@/components/repo/RepoBadgeLabel'
import { searchRepos } from '@/lib/repo-search'
import { cn } from '@/lib/utils'
import { DEFAULT_SHOW_SLEEPING_WORKSPACES } from '../../../../shared/constants'
@ -234,7 +234,7 @@ const SidebarFilter = React.memo(function SidebarFilter({
className="mx-1 my-0.5 items-center gap-2 rounded-[7px] px-2 py-1 text-[12px] leading-5 font-medium data-[selected=true]:bg-black/8 dark:data-[selected=true]:bg-white/14"
>
<span className="inline-flex min-w-0 flex-1 items-center gap-1.5">
<RepoDotLabel
<RepoBadgeLabel
name={r.displayName}
color={r.badgeColor}
className="max-w-full"

View File

@ -10,7 +10,7 @@ import {
CommandItem,
CommandList
} from '@/components/ui/command'
import RepoDotLabel from '@/components/repo/RepoDotLabel'
import RepoBadgeLabel from '@/components/repo/RepoBadgeLabel'
import { searchRepos } from '@/lib/repo-search'
import type { Repo } from '../../../../shared/types'
@ -169,7 +169,7 @@ const SidebarRepositoryFilterSection = React.memo(function SidebarRepositoryFilt
className="mx-1 my-0.5 items-center gap-2 rounded-[7px] px-2 py-1 text-[12px] leading-5 font-medium data-[selected=true]:bg-black/8 dark:data-[selected=true]:bg-white/14"
>
<span className="inline-flex min-w-0 flex-1 items-center gap-1.5">
<RepoDotLabel
<RepoBadgeLabel
name={repo.displayName}
color={repo.badgeColor}
className="max-w-full"
@ -208,11 +208,11 @@ function SelectedProjectPills({
variant="outline"
className="h-5 max-w-full gap-1 border-border/70 bg-background px-1.5 py-0 text-[11px] font-medium"
>
<RepoDotLabel
<RepoBadgeLabel
name={repo.displayName}
color={repo.badgeColor}
className="max-w-[8rem]"
dotClassName="size-1.5"
badgeClassName="size-1.5"
/>
<Button
type="button"

View File

@ -5,6 +5,7 @@ import { Badge } from '@/components/ui/badge'
import { HoverCard, HoverCardContent, HoverCardTrigger } from '@/components/ui/hover-card'
import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip'
import { activateAndRevealWorktree } from '@/lib/worktree-activation'
import { RepoBadgeMark } from '@/components/repo/RepoBadgeLabel'
import { cn } from '@/lib/utils'
import type { Repo, Worktree } from '../../../../shared/types'
import WorktreeCard from './WorktreeCard'
@ -228,10 +229,7 @@ function WorkspaceKanbanCompactCard({
className="ml-2 flex max-w-[25%] shrink-0 items-center gap-1 rounded-[4px] border border-border bg-accent px-1.5 py-0.5 leading-none dark:border-border/60 dark:bg-accent/50"
data-workspace-board-repo-badge=""
>
<span
className="size-1.5 shrink-0 rounded-full"
style={{ backgroundColor: repo.badgeColor }}
/>
<RepoBadgeMark color={repo.badgeColor} />
<span className="min-w-0 truncate text-[10px] font-semibold lowercase text-foreground">
{repo.displayName}
</span>

View File

@ -43,6 +43,7 @@ import { WorktreeCardPortsDetails, WorktreeCardPortsTrigger } from './WorktreeCa
import { writeWorkspaceDragData } from './workspace-status'
import { getWorktreeCardPrDisplay } from './worktree-card-pr-display'
import { getWorkspacePortsByWorktreeId } from '@/lib/workspace-port-groups'
import { RepoBadgeMark } from '@/components/repo/RepoBadgeLabel'
import { hasActiveWorkspaceActivity } from '@/lib/worktree-activity-state'
import { installWindowVisibilityInterval, isWindowVisible } from '@/lib/window-visibility-interval'
import { runWorktreeDelete } from './delete-worktree-flow'
@ -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()
@ -718,10 +718,7 @@ const WorktreeCard = React.memo(function WorktreeCard({
<div className="flex min-w-0 flex-1 items-center gap-1.5 overflow-hidden">
{repo && !hideRepoBadge && (
<div className="flex items-center gap-1.5 shrink-0 px-1.5 py-0.5 rounded-[4px] bg-accent border border-border dark:bg-accent/50 dark:border-border/60">
<div
className="size-1.5 rounded-full"
style={{ backgroundColor: repo.badgeColor }}
/>
<RepoBadgeMark color={repo.badgeColor} />
<span className="text-[10px] font-semibold text-foreground truncate max-w-[6rem] leading-none lowercase">
{repo.displayName}
</span>

View File

@ -137,6 +137,7 @@ import {
isLegacyRepoForExternalWorktreeVisibility
} from '../../../../shared/worktree-ownership'
import { RepoIconGlyph } from '@/components/repo/repo-icon'
import { RepoBadgeMark } from '@/components/repo/RepoBadgeLabel'
// How long to wait after a sortEpoch bump before actually re-sorting.
// Prevents jarring position shifts when background events (AI starting work,
@ -2214,10 +2215,7 @@ const VirtualizedWorktreeViewport = React.memo(function VirtualizedWorktreeViewp
<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 }}
/>
<RepoBadgeMark color={child.repo.badgeColor} />
<span className="max-w-[6rem] truncate lowercase">
{child.repo.displayName}
</span>

View File

@ -25,7 +25,7 @@ import {
SelectValue
} from '@/components/ui/select'
import { ToggleGroup, ToggleGroupItem } from '@/components/ui/toggle-group'
import RepoDotLabel from '@/components/repo/RepoDotLabel'
import RepoBadgeLabel from '@/components/repo/RepoBadgeLabel'
import { isMacUserAgent } from '@/components/terminal-pane/pane-helpers'
type TerminalQuickCommandDialogMode = 'add' | 'edit'
@ -187,7 +187,7 @@ export function TerminalQuickCommandDialog({
<SelectContent>
{repos.map((repo) => (
<SelectItem key={repo.id} value={repo.id}>
<RepoDotLabel
<RepoBadgeLabel
name={getRepoLabel(repo)}
color={repo.badgeColor}
className="max-w-full"

View File

@ -12,7 +12,7 @@ import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover
import { searchRepos } from '@/lib/repo-search'
import { cn } from '@/lib/utils'
import type { Repo } from '../../../../shared/types'
import RepoDotLabel from '@/components/repo/RepoDotLabel'
import RepoBadgeLabel from '@/components/repo/RepoBadgeLabel'
type RepoMultiComboboxProps = {
repos: Repo[]
@ -42,7 +42,11 @@ function renderTriggerLabel(repos: Repo[], selected: ReadonlySet<string>): React
return (
<span className="inline-flex min-w-0 items-center gap-1.5 truncate">
{first ? (
<RepoDotLabel name={first.displayName} color={first.badgeColor} dotClassName="size-1.5" />
<RepoBadgeLabel
name={first.displayName}
color={first.badgeColor}
badgeClassName="size-1.5"
/>
) : null}
{second ? <span className="text-muted-foreground">, {second.displayName}</span> : null}
{rest.length > 0 ? <span className="text-muted-foreground">+{rest.length}</span> : null}
@ -181,7 +185,7 @@ export default function RepoMultiCombobox({
/>
<div className="min-w-0 flex-1">
<span className="inline-flex items-center gap-1.5 text-xs">
<RepoDotLabel
<RepoBadgeLabel
name={repo.displayName}
color={repo.badgeColor}
className="max-w-full"