diff --git a/src/components/StatusBar.test.tsx b/src/components/StatusBar.test.tsx index d124cc60..0467c9b2 100644 --- a/src/components/StatusBar.test.tsx +++ b/src/components/StatusBar.test.tsx @@ -353,21 +353,42 @@ describe('StatusBar', () => { }) it('collapses status labels to icon-first controls at very narrow widths', () => { - setWindowWidth(760) + setWindowWidth(880) renderDenseStatusBar() expect(screen.getByTestId('status-bar')).toHaveStyle({ - flexWrap: 'wrap', - height: 'auto', + flexWrap: 'nowrap', + height: '30px', }) expect(screen.getByTestId('status-commit-push')).toBeInTheDocument() expect(screen.getByTestId('status-pulse')).toBeInTheDocument() expect(screen.getByTestId('status-feedback')).toBeInTheDocument() + expect(screen.getByTestId('status-build-number')).toBeInTheDocument() + expect(screen.getByTestId('status-claude-code')).toBeInTheDocument() expect(screen.queryByText('Commit')).not.toBeInTheDocument() expect(screen.queryByText('History')).not.toBeInTheDocument() expect(screen.queryByText('Contribute')).not.toBeInTheDocument() expect(screen.queryByText('No remote')).not.toBeInTheDocument() expect(screen.queryByText('MCP')).not.toBeInTheDocument() + expect(screen.queryByText('b281')).not.toBeInTheDocument() + expect(screen.queryByText('Claude Code missing')).not.toBeInTheDocument() + }) + + it('hides the active AI agent label in compact status layout', () => { + setWindowWidth(880) + render( + + ) + + expect(screen.getByTestId('status-ai-agents')).toBeInTheDocument() + expect(screen.queryByText('Claude')).not.toBeInTheDocument() }) it('does not show Changes badge when modifiedCount is 0', () => { diff --git a/src/components/StatusBar.tsx b/src/components/StatusBar.tsx index 3ddc44fe..2f71969b 100644 --- a/src/components/StatusBar.tsx +++ b/src/components/StatusBar.tsx @@ -15,16 +15,18 @@ import type { VaultOption } from './status-bar/types' export type { VaultOption } from './status-bar/types' const STACKED_STATUS_BAR_MAX_WIDTH = 1040 -const COMPACT_STATUS_BAR_MAX_WIDTH = 820 +const COMPACT_STATUS_BAR_MAX_WIDTH = 900 function getWindowWidth() { return typeof window === 'undefined' ? Number.POSITIVE_INFINITY : window.innerWidth } function getStatusBarLayout(windowWidth: number) { + const compact = windowWidth <= COMPACT_STATUS_BAR_MAX_WIDTH + return { - compact: windowWidth <= COMPACT_STATUS_BAR_MAX_WIDTH, - stacked: windowWidth <= STACKED_STATUS_BAR_MAX_WIDTH, + compact, + stacked: !compact && windowWidth <= STACKED_STATUS_BAR_MAX_WIDTH, } } @@ -154,7 +156,7 @@ function StatusBarFooter({ alignItems: stacked ? 'flex-start' : 'center', justifyContent: stacked ? 'flex-start' : 'space-between', rowGap: stacked ? 4 : 0, - columnGap: 12, + columnGap: compact ? 8 : 12, background: 'var(--sidebar)', borderTop: '1px solid var(--border)', padding: stacked ? '4px 8px' : '0 8px', diff --git a/src/components/status-bar/AiAgentsBadge.tsx b/src/components/status-bar/AiAgentsBadge.tsx index f209b299..f6c1dbfd 100644 --- a/src/components/status-bar/AiAgentsBadge.tsx +++ b/src/components/status-bar/AiAgentsBadge.tsx @@ -38,6 +38,7 @@ interface AiAgentsBadgeProps { defaultAgent: AiAgentId onSetDefaultAgent?: (agent: AiAgentId) => void onRestoreGuidance?: () => void + compact?: boolean } function badgeTooltip( @@ -95,6 +96,48 @@ function canSwitchAgents( return installedAgents.some((definition) => definition.id !== defaultAgent) } +function hasAiAgentWarning( + statuses: AiAgentsStatus, + defaultAgent: AiAgentId, + guidanceStatus?: VaultAiGuidanceStatus, +): boolean { + return !hasAnyInstalledAiAgent(statuses) + || !isAiAgentInstalled(statuses, defaultAgent) + || !!(guidanceStatus && vaultAiGuidanceNeedsRestore(guidanceStatus)) +} + +function canShowSwitcherCue(statuses: AiAgentsStatus, defaultAgent: AiAgentId): boolean { + return canSwitchAgents(installedAgentDefinitions(statuses), defaultAgent) +} + +function triggerButtonClassName(compact: boolean): string { + return compact + ? 'h-6 w-6 rounded-sm p-0 text-[11px] font-medium' + : 'h-6 px-2 text-[11px] font-medium' +} + +function CompactSeparator({ compact }: { compact: boolean }) { + if (compact) return null + return | +} + +function TriggerLabel({ compact, defaultAgent }: { compact: boolean; defaultAgent: AiAgentId }) { + if (compact) return null + return triggerLabel(defaultAgent) +} + +function TriggerStateIcon({ + showWarning, + showSwitcherCue, +}: { + showWarning: boolean + showSwitcherCue: boolean +}) { + if (showWarning) return + if (showSwitcherCue) return + return null +} + function GuidanceMenuSection({ guidanceStatus, onRestoreGuidance, @@ -184,19 +227,17 @@ export function AiAgentsBadge({ defaultAgent, onSetDefaultAgent, onRestoreGuidance, + compact = false, }: AiAgentsBadgeProps) { - const hasInstalledAgent = hasAnyInstalledAiAgent(statuses) const selectedAgentReady = isAiAgentInstalled(statuses, defaultAgent) - const showWarning = !hasInstalledAgent - || !selectedAgentReady - || !!(guidanceStatus && vaultAiGuidanceNeedsRestore(guidanceStatus)) - const showSwitcherCue = canSwitchAgents(installedAgentDefinitions(statuses), defaultAgent) + const showWarning = hasAiAgentWarning(statuses, defaultAgent, guidanceStatus) + const showSwitcherCue = !showWarning && canShowSwitcherCue(statuses, defaultAgent) if (isAiAgentsStatusChecking(statuses)) return null return ( <> - | + @@ -204,15 +245,14 @@ export function AiAgentsBadge({ type="button" variant="ghost" size="xs" - className="h-6 px-2 text-[11px] font-medium" + className={triggerButtonClassName(compact)} aria-label="Open AI agent options" data-testid="status-ai-agents" > - {triggerLabel(defaultAgent)} - {showWarning && } - {!showWarning && showSwitcherCue && } + + diff --git a/src/components/status-bar/StatusBarSections.tsx b/src/components/status-bar/StatusBarSections.tsx index bb3d1267..0220bcec 100644 --- a/src/components/status-bar/StatusBarSections.tsx +++ b/src/components/status-bar/StatusBarSections.tsx @@ -118,7 +118,7 @@ function BuildNumberButton({ > - {buildNumber ?? 'b?'} + {compact ? null : buildNumber ?? 'b?'} @@ -153,6 +153,7 @@ function StatusBarAiBadge({ defaultAgent={defaultAiAgent} onSetDefaultAgent={onSetDefaultAiAgent} onRestoreGuidance={onRestoreVaultAiGuidance} + compact={compact} /> ) }