Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions src/components/TabItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ import type { MouseEvent, KeyboardEvent } from 'react'
import { ContextIds } from '@/components/context-menu/context-menu-constants'

function StatusDot({ status, activityPulse }: { status: TerminalStatus; activityPulse?: boolean }) {
return <Circle className={cn('h-2 w-2', getTerminalStatusDotClassName(status), activityPulse && status === 'running' && 'animate-pulse')} />
const isBusy = activityPulse && status === 'running'
return <Circle className={cn('h-2 w-2', isBusy ? 'fill-blue-500 text-blue-500' : getTerminalStatusDotClassName(status))} />
}

const MAX_TAB_ICONS = 6
Expand Down Expand Up @@ -96,14 +97,13 @@ export default function TabItem({
content={content}
className={cn(
'h-3 w-3 shrink-0',
getTerminalStatusIconClassName(status),
shouldPulse && 'animate-pulse',
shouldPulse ? 'text-blue-500' : getTerminalStatusIconClassName(status),
)}
/>
)
})}
{overflow > 0 && (
<span className={cn('text-[10px] text-muted-foreground leading-none', hiddenBusyTerminal && 'animate-pulse')}>+{overflow}</span>
<span className={cn('text-[10px] leading-none', hiddenBusyTerminal ? 'text-blue-500' : 'text-muted-foreground')}>+{overflow}</span>
)}
</span>
)
Expand Down
21 changes: 11 additions & 10 deletions test/unit/client/components/TabBar.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -279,7 +279,7 @@ describe('TabBar', () => {
expect(scrollContainer).toBeNull()
})

it('pulses a tab when any exact terminal id in that tab is busy', () => {
it('shows blue color on a tab when any exact terminal id in that tab is busy', () => {
const tab = createTab({
id: 'tab-codex',
title: 'Codex Tab',
Expand Down Expand Up @@ -307,11 +307,12 @@ describe('TabBar', () => {
const busyIcon = icons.find((icon) => icon.getAttribute('data-terminal-id') === 'term-1')
const idleIcon = icons.find((icon) => icon.getAttribute('data-terminal-id') === 'term-2')

expect(busyIcon?.getAttribute('class')).toContain('animate-pulse')
expect(idleIcon?.getAttribute('class') ?? '').not.toContain('animate-pulse')
expect(busyIcon?.getAttribute('class')).toContain('text-blue-500')
expect(busyIcon?.getAttribute('class')).not.toContain('animate-pulse')
expect(idleIcon?.getAttribute('class') ?? '').not.toContain('text-blue-500')
})

it('does not pulse a tab when the exact record is only pending', () => {
it('does not show blue on a tab when the exact record is only pending', () => {
const tab = createTab({
id: 'tab-codex',
title: 'Codex Pending',
Expand Down Expand Up @@ -350,10 +351,10 @@ describe('TabBar', () => {
renderWithStore(<TabBar />, store)

const tabElement = screen.getByLabelText('Codex Pending')
const pulsingIcons = within(tabElement).getAllByTestId('pane-icon')
.filter((icon) => icon.getAttribute('class')?.includes('animate-pulse'))
const blueIcons = within(tabElement).getAllByTestId('pane-icon')
.filter((icon) => icon.getAttribute('class')?.includes('text-blue-500'))

expect(pulsingIcons).toHaveLength(0)
expect(blueIcons).toHaveLength(0)
})

it('falls back to the exact tab terminal id for a single-pane rehydrate gap', () => {
Expand Down Expand Up @@ -395,10 +396,10 @@ describe('TabBar', () => {
renderWithStore(<TabBar />, store)

const tabElement = screen.getByLabelText('Rehydrate Gap')
const pulsingIcons = within(tabElement).getAllByTestId('pane-icon')
.filter((icon) => icon.getAttribute('class')?.includes('animate-pulse'))
const blueIcons = within(tabElement).getAllByTestId('pane-icon')
.filter((icon) => icon.getAttribute('class')?.includes('text-blue-500'))

expect(pulsingIcons.length).toBeGreaterThan(0)
expect(blueIcons.length).toBeGreaterThan(0)
})
})

Expand Down
17 changes: 10 additions & 7 deletions test/unit/client/components/TabItem.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,7 @@ describe('TabItem', () => {
expect(screen.getByDisplayValue('Editing')).toBeInTheDocument()
})

it('pulses only the exact busy terminal icon in split tabs', () => {
it('shows blue color on the exact busy terminal icon in split tabs', () => {
const paneContents: PaneContent[] = [
{
kind: 'terminal',
Expand Down Expand Up @@ -188,11 +188,12 @@ describe('TabItem', () => {
const busyIcon = icons.find((icon) => icon.getAttribute('data-terminal-id') === 'term-1')
const idleIcon = icons.find((icon) => icon.getAttribute('data-terminal-id') === 'term-2')

expect(busyIcon?.getAttribute('class')).toContain('animate-pulse')
expect(idleIcon?.getAttribute('class') ?? '').not.toContain('animate-pulse')
expect(busyIcon?.getAttribute('class')).toContain('text-blue-500')
expect(busyIcon?.getAttribute('class')).not.toContain('animate-pulse')
expect(idleIcon?.getAttribute('class') ?? '').not.toContain('text-blue-500')
})

it('pulses a single unnamed terminal icon during the exact tab-terminal fallback', () => {
it('shows blue color on a single unnamed terminal icon during the exact tab-terminal fallback', () => {
const paneContents: PaneContent[] = [
{
kind: 'terminal',
Expand All @@ -213,10 +214,11 @@ describe('TabItem', () => {
/>
)

expect(screen.getByTestId('pane-icon').getAttribute('class')).toContain('animate-pulse')
expect(screen.getByTestId('pane-icon').getAttribute('class')).toContain('text-blue-500')
expect(screen.getByTestId('pane-icon').getAttribute('class')).not.toContain('animate-pulse')
})

it('pulses the overflow indicator when the exact busy terminal is hidden beyond the visible icon cap', () => {
it('shows blue color on the overflow indicator when the exact busy terminal is hidden beyond the visible icon cap', () => {
const paneContents: PaneContent[] = Array.from({ length: 7 }, (_, index) => ({
kind: 'terminal',
mode: 'shell',
Expand All @@ -235,7 +237,8 @@ describe('TabItem', () => {
/>
)

expect(screen.getByText('+1').getAttribute('class')).toContain('animate-pulse')
expect(screen.getByText('+1').getAttribute('class')).toContain('text-blue-500')
expect(screen.getByText('+1').getAttribute('class')).not.toContain('animate-pulse')
})

it('calls onClick when clicked', () => {
Expand Down