From fee70ee08021706ccfadf1d10c78d5dd679af26b Mon Sep 17 00:00:00 2001
From: Jinjing <6427696+AmethystLiang@users.noreply.github.com>
Date: Thu, 10 Sep 2026 15:21:14 -0700
Subject: [PATCH] feat(cmd-j): compact palette location layout
---
...ee-jump-palette-browser-simulator-rows.tsx | 41 ++--
...jump-palette-interleaved-sections.test.tsx | 7 +-
.../worktree-jump-palette-primitives.test.tsx | 93 ++++++++-
.../worktree-jump-palette-primitives.tsx | 187 ++++++++++--------
...orktree-jump-palette-workspace-tab-row.tsx | 22 +--
5 files changed, 226 insertions(+), 124 deletions(-)
diff --git a/src/renderer/src/components/worktree-jump-palette-browser-simulator-rows.tsx b/src/renderer/src/components/worktree-jump-palette-browser-simulator-rows.tsx
index 95220a25f2e..7ede7b29e03 100644
--- a/src/renderer/src/components/worktree-jump-palette-browser-simulator-rows.tsx
+++ b/src/renderer/src/components/worktree-jump-palette-browser-simulator-rows.tsx
@@ -1,15 +1,14 @@
import type React from 'react'
import { Smartphone } from 'lucide-react'
import { CommandItem } from '@/components/ui/command'
-import { RepoBadgeMark } from '@/components/repo/RepoBadgeLabel'
import { getPaletteHostBadge } from '@/components/cmd-j/palette-host-badge'
import { cn } from '@/lib/utils'
import { translate } from '@/i18n/i18n'
import type { BrowserPaletteItem, SimulatorPaletteItem } from './worktree-jump-palette-model'
import type { WorktreeJumpPaletteController } from './use-worktree-jump-palette-controller'
import {
- HighlightedText,
PaletteHostBadgeChip,
+ PaletteLocationChip,
PaletteOpenTabPrimaryLine,
PaletteRowShortcutBadge
} from './worktree-jump-palette-primitives'
@@ -67,8 +66,6 @@ export function WorktreeJumpPaletteSimulatorRow({
secondaryText={result.secondaryText}
secondaryRanges={result.secondaryRanges}
secondaryMatches={result.secondaryMatches}
- worktreeName={result.worktreeName}
- worktreeRanges={result.worktreeRanges}
sessionAge={simulatorSessionAge}
leadingBadges={
<>
@@ -94,16 +91,15 @@ export function WorktreeJumpPaletteSimulatorRow({
) : null}
-
+
- {simulatorRepoName && (
-
-
-
-
-
-
- )}
+
@@ -187,16 +181,15 @@ export function WorktreeJumpPaletteBrowserRow({
}
/>
-
+
- {browserRepoName && (
-
-
-
-
-
-
- )}
+
{
const title = row?.querySelector('[data-slot="palette-open-tab-title"]')
const worktree = row?.querySelector('[data-slot="palette-open-tab-worktree"]')
expect(title?.textContent).toBe(longTitle)
- expect(title?.classList.contains('flex-auto')).toBe(true)
+ expect(title?.classList.contains('min-w-0')).toBe(true)
+ expect(title?.classList.contains('shrink-0')).toBe(false)
expect(worktree?.textContent).toBe('user-support')
+ const locationChip = worktree?.closest('[data-slot="palette-location-chip"]')
+ expect(locationChip).not.toBeNull()
+ expect(locationChip?.parentElement?.classList.contains('max-w-[40%]')).toBe(true)
+ expect(locationChip?.parentElement?.classList.contains('min-w-0')).toBe(true)
expect(worktree?.compareDocumentPosition(title ?? document.createElement('span'))).toBe(
Node.DOCUMENT_POSITION_PRECEDING
)
diff --git a/src/renderer/src/components/worktree-jump-palette-primitives.test.tsx b/src/renderer/src/components/worktree-jump-palette-primitives.test.tsx
index a54a48329be..39a5f6be3af 100644
--- a/src/renderer/src/components/worktree-jump-palette-primitives.test.tsx
+++ b/src/renderer/src/components/worktree-jump-palette-primitives.test.tsx
@@ -3,7 +3,7 @@
import { cleanup, render, type RenderResult, screen } from '@testing-library/react'
import { afterEach, expect, it } from 'vitest'
import { TooltipProvider } from '@/components/ui/tooltip'
-import { PaletteOpenTabPrimaryLine } from './worktree-jump-palette-primitives'
+import { PaletteLocationChip, PaletteOpenTabPrimaryLine } from './worktree-jump-palette-primitives'
afterEach(() => cleanup())
@@ -18,8 +18,6 @@ function renderPrimaryLine(
secondaryText="src/app.ts"
secondaryRanges={[]}
secondaryMatches={secondaryMatches}
- worktreeName="Workspace"
- worktreeRanges={[]}
/>
)
@@ -45,3 +43,92 @@ it('renders no badge when every secondary match is already shown', () => {
expect(screen.queryByText(/^\+\d+$/)).toBeNull()
})
+
+it('elides a deep path from the head so the matched tail stays visible', () => {
+ const path = '/Users/me/projects/orca/new-create-button-design/proposals/create-button.html'
+ const start = path.indexOf('create-butt')
+ const { container } = render(
+
+
+
+ )
+
+ const secondary = container.querySelector('[data-slot="palette-open-tab-secondary"]')
+ expect(secondary?.textContent).toBe(path)
+ const [head, tail] = Array.from(secondary?.children ?? [])
+ expect(head?.textContent).toBe('/Users/me/projects/orca')
+ expect(tail?.textContent).toBe('/new-create-button-design/proposals/create-button.html')
+ expect(tail?.querySelector('.font-semibold')?.textContent).toBe('create-butt')
+})
+
+it('folds the worktree into the repo chip and drops it when it repeats the repo name', () => {
+ const { container, rerender } = render(
+
+
+
+ )
+ expect(container.querySelector('[data-slot="palette-location-chip"]')?.textContent).toBe(
+ 'orca·new-create-button-design'
+ )
+ const chip = container.querySelector('[data-slot="palette-location-chip"]')
+ const repo = container.querySelector('[data-slot="palette-location-repo"]')
+ const worktree = container.querySelector('[data-slot="palette-open-tab-worktree"]')
+ expect(chip?.className).toContain('overflow-hidden')
+ expect(chip?.className).toContain('min-w-0')
+ expect(repo?.className).toContain('max-w-[55%]')
+ expect(repo?.className).toContain('min-w-[3ch]')
+ expect(worktree?.className).toContain('min-w-[3ch]')
+ expect(worktree?.getAttribute('data-state')).toBeNull()
+ expect(worktree?.getAttribute('tabindex')).toBeNull()
+
+ rerender(
+
+
+
+ )
+ expect(container.querySelector('[data-slot="palette-location-chip"]')?.textContent).toBe('orca')
+ expect(
+ container.querySelector('[data-slot="palette-location-repo"] .font-semibold')?.textContent
+ ).toBe('orca')
+})
+
+it('keeps a short title at its natural width so the session age stays beside it', () => {
+ const { container } = render(
+
+
+
+ )
+
+ const title = container.querySelector('[data-slot="palette-open-tab-title"]')
+ expect(title?.className).toContain('min-w-0')
+ expect(title?.className).not.toContain('shrink-0')
+})
+
+it('caps the title only when it shares the line with secondary text', () => {
+ const { container } = renderPrimaryLine([])
+
+ const title = container.querySelector('[data-slot="palette-open-tab-title"]')
+ expect(title?.className).toContain('max-w-[62%]')
+ expect(title?.className).toContain('shrink-0')
+})
diff --git a/src/renderer/src/components/worktree-jump-palette-primitives.tsx b/src/renderer/src/components/worktree-jump-palette-primitives.tsx
index 4162a56cb6e..7c7c01585fc 100644
--- a/src/renderer/src/components/worktree-jump-palette-primitives.tsx
+++ b/src/renderer/src/components/worktree-jump-palette-primitives.tsx
@@ -1,11 +1,12 @@
-import React, { useLayoutEffect, useRef, useState } from 'react'
+import React from 'react'
import { ShortcutKeyCombo } from '@/components/ShortcutKeyCombo'
import { translate } from '@/i18n/i18n'
import type { PaletteHostBadge } from '@/components/cmd-j/palette-host-badge'
import type { MatchRange, PaletteSearchResult } from '@/lib/worktree-palette-search'
import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip'
-import type { Worktree } from '../../../shared/worktree/types'
-import { resolveWorktreeBranchLabel } from '@/lib/worktree-default-display-name'
+import { splitPathHeadForElision } from '@/lib/path-head-elision'
+import { RepoBadgeMark } from '@/components/repo/RepoBadgeLabel'
+import { cn } from '@/lib/utils'
const NO_SECONDARY_MATCHES: readonly { text: string; ranges: readonly MatchRange[] }[] = []
@@ -67,14 +68,48 @@ export function HighlightedText({
return <>{parts}>
}
+function PaletteOpenTabSecondaryText({
+ text,
+ ranges
+}: {
+ text: string
+ ranges: readonly MatchRange[]
+}): React.JSX.Element {
+ const split = splitPathHeadForElision(text, ranges)
+ const content = split ? (
+
+ {split.head}
+
+
+
+
+ ) : (
+
+
+
+ )
+ return (
+
+ {content}
+
+ {text}
+
+
+ )
+}
+
export function PaletteOpenTabPrimaryLine({
title,
titleRanges,
secondaryText,
secondaryRanges,
secondaryMatches = NO_SECONDARY_MATCHES,
- worktreeName,
- worktreeRanges,
sessionAge,
leadingBadges
}: {
@@ -83,13 +118,10 @@ export function PaletteOpenTabPrimaryLine({
secondaryText: string
secondaryRanges: readonly MatchRange[]
secondaryMatches?: readonly { text: string; ranges: readonly MatchRange[] }[]
- worktreeName: string
- worktreeRanges: readonly MatchRange[]
sessionAge?: string
leadingBadges?: React.ReactNode
}): React.JSX.Element {
const showSecondary = secondaryText.trim().length > 0
- const showWorktree = worktreeName.trim().length > 0
const additionalSecondaryMatches = secondaryMatches.filter(
(match) => match.text && match.text !== secondaryText
)
@@ -98,7 +130,10 @@ export function PaletteOpenTabPrimaryLine({
@@ -116,12 +151,7 @@ export function PaletteOpenTabPrimaryLine({
) : null}
{leadingBadges}
{showSecondary ? (
- <>
-
·
-
-
-
- >
+
) : null}
{additionalSecondaryMatches.length ? (
<>
@@ -154,85 +184,76 @@ export function PaletteOpenTabPrimaryLine({
>
) : null}
- {showWorktree ? (
- <>
-
·
-
-
-
- >
- ) : null}
)
}
-function resolveOpenTabWorktreeRailTooltip({
- isBranch,
- truncated,
- name
+export function PaletteLocationChip({
+ repoName,
+ repoRanges,
+ repoColor,
+ worktreeName,
+ worktreeRanges,
+ className
}: {
- isBranch: boolean
- truncated: boolean
- name: string
-}): string {
- if (truncated) {
- return name
- }
- return isBranch
- ? translate('auto.components.WorktreeJumpPalette.paletteOpenTabBranch', 'Branch name')
- : translate('auto.components.WorktreeJumpPalette.paletteOpenTabWorkspace', 'Workspace name')
-}
-
-export function PaletteOpenTabWorktreeRailLabel({
- name,
- matchRanges,
- worktree,
- className,
- slot = 'palette-open-tab-worktree'
-}: {
- name: string
- matchRanges: readonly MatchRange[]
- worktree?: Pick | null
+ repoName: string
+ repoRanges: readonly MatchRange[]
+ repoColor?: string
+ worktreeName: string
+ worktreeRanges: readonly MatchRange[]
className?: string
- slot?: string
}): React.JSX.Element | null {
- const [truncated, setTruncated] = useState(false)
- const labelRef = useRef(null)
- useLayoutEffect(() => {
- const node = labelRef.current
- if (!node) {
- setTruncated(false)
- return
- }
- const updateTruncated = (): void => {
- const next = node.scrollWidth > node.clientWidth
- setTruncated((current) => (current === next ? current : next))
- }
- updateTruncated()
- if (typeof ResizeObserver === 'undefined') {
- return
- }
- const observer = new ResizeObserver(updateTruncated)
- observer.observe(node)
- return () => observer.disconnect()
- }, [name])
- if (name.trim().length === 0) {
+ const showRepo = repoName.trim().length > 0
+ const repeatedName = showRepo && worktreeName === repoName
+ const showWorktree = worktreeName.trim().length > 0 && !repeatedName
+ if (!showRepo && !showWorktree) {
return null
}
- const isBranch = worktree != null && name === resolveWorktreeBranchLabel(worktree)
- const tooltip = resolveOpenTabWorktreeRailTooltip({ isBranch, truncated, name })
+ const repoMatchRanges = repeatedName
+ ? [...repoRanges, ...worktreeRanges].sort((left, right) => left.start - right.start)
+ : repoRanges
+ const label = [showRepo ? repoName : '', showWorktree ? worktreeName : '']
+ .filter(Boolean)
+ .join(' · ')
+ const chip = (
+
+ {showRepo ? (
+ <>
+
+
+
+
+ >
+ ) : null}
+ {showRepo && showWorktree ? (
+
+ ·
+
+ ) : null}
+ {showWorktree ? (
+
+
+
+ ) : null}
+
+ )
return (
-
-
-
-
-
-
- {tooltip}
+ {chip}
+
+ {label}
)
diff --git a/src/renderer/src/components/worktree-jump-palette-workspace-tab-row.tsx b/src/renderer/src/components/worktree-jump-palette-workspace-tab-row.tsx
index 787cbff449c..f60131191ef 100644
--- a/src/renderer/src/components/worktree-jump-palette-workspace-tab-row.tsx
+++ b/src/renderer/src/components/worktree-jump-palette-workspace-tab-row.tsx
@@ -3,15 +3,14 @@ import { FileText, SquareTerminal } from 'lucide-react'
import { AgentIcon } from '@/lib/agent-catalog'
import { CommandItem } from '@/components/ui/command'
import { PaletteRecentTabStatusDot } from '@/components/cmd-j/palette-live-status'
-import { RepoBadgeMark } from '@/components/repo/RepoBadgeLabel'
import { getPaletteHostBadge } from '@/components/cmd-j/palette-host-badge'
import { cn } from '@/lib/utils'
import { translate } from '@/i18n/i18n'
import type { WorkspaceTabPaletteItem } from './worktree-jump-palette-model'
import type { WorktreeJumpPaletteController } from './use-worktree-jump-palette-controller'
import {
- HighlightedText,
PaletteHostBadgeChip,
+ PaletteLocationChip,
PaletteOpenTabPrimaryLine,
PaletteRowShortcutBadge
} from './worktree-jump-palette-primitives'
@@ -76,8 +75,6 @@ export function WorktreeJumpPaletteWorkspaceTabRow({
secondaryText={result.secondaryText}
secondaryRanges={result.secondaryRanges}
secondaryMatches={result.secondaryMatches}
- worktreeName={result.worktreeName}
- worktreeRanges={result.worktreeRanges}
sessionAge={sessionAge}
leadingBadges={
<>
@@ -103,16 +100,15 @@ export function WorktreeJumpPaletteWorkspaceTabRow({
) : null}
-
+
- {workspaceTabRepoName && (
-
-
-
-
-
-
- )}
+