From 5c738db3af765effb0315847ecf331731bb40fba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=A8=8B=E5=BA=8F=E5=91=98=E9=98=BF=E6=B1=9F=28Relakkes?= =?UTF-8?q?=29?= Date: Thu, 7 May 2026 19:59:19 +0800 Subject: [PATCH] fix: keep launch context rail on one line Long branch names should be clipped inside the branch selector instead of forcing the repository context rail onto a second row. The rail now stays nowrap, the branch selector owns truncation, and the worktree mode selector keeps its readable fixed footprint. Constraint: Preserve the existing composer and repository context layout shape. Rejected: Let the rail wrap on long branch names | it creates the broken two-line state shown in the desktop composer. Confidence: high Scope-risk: narrow Directive: Long repository or branch labels must truncate inside their own controls rather than changing the rail height. Tested: cd desktop && bun run test -- --run src/pages/EmptySession.test.tsx src/components/chat/ChatInput.test.tsx src/components/shared/DirectoryPicker.test.tsx Tested: bun run check:desktop Tested: agent-browser selected a long branch on http://localhost:4790 and verified rail height 53px, branch width 260px, no horizontal overflow; screenshot /tmp/cc-haha-launch-rail-long-branch-nowrap.png Not-tested: Native Tauri packaged window visual check --- .../shared/RepositoryLaunchControls.tsx | 8 ++--- desktop/src/pages/EmptySession.test.tsx | 36 +++++++++++++++++++ 2 files changed, 40 insertions(+), 4 deletions(-) diff --git a/desktop/src/components/shared/RepositoryLaunchControls.tsx b/desktop/src/components/shared/RepositoryLaunchControls.tsx index 1159fe07..d2da78af 100644 --- a/desktop/src/components/shared/RepositoryLaunchControls.tsx +++ b/desktop/src/components/shared/RepositoryLaunchControls.tsx @@ -298,11 +298,11 @@ export function RepositoryLaunchControls({ }, [isLaunchReady, onLaunchReadyChange]) const worktreeLabel = useWorktree ? t('repoLaunch.worktreeIsolated') : t('repoLaunch.worktreeCurrent') - const workbarButtonClassName = 'group inline-flex h-9 max-w-full min-w-0 items-center gap-1.5 rounded-[7px] border border-transparent px-2.5 text-[13px] font-medium leading-none text-[var(--color-text-secondary)] transition-colors hover:bg-[var(--color-surface-container-lowest)] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--color-brand)]/35 disabled:cursor-not-allowed disabled:opacity-50' + const workbarButtonClassName = 'group inline-flex h-9 min-w-0 items-center gap-1.5 rounded-[7px] border border-transparent px-2.5 text-[13px] font-medium leading-none text-[var(--color-text-secondary)] transition-colors hover:bg-[var(--color-surface-container-lowest)] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--color-brand)]/35 disabled:cursor-not-allowed disabled:opacity-50' return (
-
+
{loading && workDir && ( @@ -328,7 +328,7 @@ export function RepositoryLaunchControls({ setWorktreeMenuOpen(false) setBranchFilter('') }} - className={`${workbarButtonClassName} max-w-[260px]`} + className={`${workbarButtonClassName} max-w-[260px] shrink`} > @@ -350,7 +350,7 @@ export function RepositoryLaunchControls({ setWorktreeMenuOpen((prev) => !prev) setBranchMenuOpen(false) }} - className={`${workbarButtonClassName} ${ + className={`${workbarButtonClassName} shrink-0 ${ useWorktree ? 'bg-[var(--color-surface-container-lowest)] text-[var(--color-text-primary)]' : '' diff --git a/desktop/src/pages/EmptySession.test.tsx b/desktop/src/pages/EmptySession.test.tsx index 4e1d7583..52fdcf34 100644 --- a/desktop/src/pages/EmptySession.test.tsx +++ b/desktop/src/pages/EmptySession.test.tsx @@ -304,6 +304,42 @@ describe('EmptySession', () => { }) }) + it('keeps the repository launch context on one row and truncates long branch names', async () => { + const longBranch = 'feature/super-long-branch-name-for-repository-launch-controls-e2e' + mocks.getRepositoryContext.mockResolvedValueOnce(okRepositoryContext({ + currentBranch: longBranch, + defaultBranch: 'main', + branches: [{ + name: longBranch, + current: true, + local: true, + remote: false, + checkedOut: true, + worktreePath: '/workspace/project', + }], + worktrees: [{ + path: '/workspace/project', + branch: longBranch, + current: true, + }], + })) + + render() + + fireEvent.change(screen.getByRole('textbox'), { + target: { value: 'draft question', selectionStart: 14 }, + }) + fireEvent.click(screen.getByRole('button', { name: 'Pick project' })) + + const branchButton = await screen.findByRole('button', { name: new RegExp(`Select branch: ${longBranch}`) }) + const branchClasses = branchButton.className.split(/\s+/) + expect(branchButton.parentElement?.className).toContain('flex-nowrap') + expect(branchButton.parentElement?.className).not.toContain('flex-wrap') + expect(branchClasses).toContain('max-w-[260px]') + expect(branchClasses).not.toContain('max-w-full') + expect(branchButton.querySelector('span')?.className).toContain('truncate') + }) + it('defaults to isolated worktree when the fallback branch is checked out elsewhere', async () => { mocks.getRepositoryContext.mockResolvedValueOnce(okRepositoryContext({ currentBranch: null,