From e9ef0a5b9582983f969eeebb597f1fc8962a7c80 Mon Sep 17 00:00:00 2001 From: Brennan Benson <79079362+brennanb2025@users.noreply.github.com> Date: Wed, 22 Jul 2026 15:49:00 -0700 Subject: [PATCH] test(agent-status): cover needs-you glyph surfaces --- .../src/components/AgentStateDot.test.ts | 6 ++--- src/renderer/src/components/AgentStateDot.tsx | 5 ---- .../dashboard-popout/AgentKanbanCard.test.tsx | 24 +++++++++++++++++ .../dashboard-popout/AgentKanbanCard.tsx | 4 +-- .../dashboard/DashboardAgentRow.test.tsx | 4 +-- .../components/sidebar/SidebarNav.test.tsx | 26 +++++++++++++++++++ .../src/components/sidebar/SidebarNav.tsx | 4 --- .../sidebar/StatusIndicator.test.ts | 6 ++--- .../components/sidebar/StatusIndicator.tsx | 4 --- .../sidebar/WorktreeCardStatusSlot.test.tsx | 6 ++--- .../tab-bar/TerminalTabLeadingIcon.test.tsx | 4 +-- 11 files changed, 59 insertions(+), 34 deletions(-) diff --git a/src/renderer/src/components/AgentStateDot.test.ts b/src/renderer/src/components/AgentStateDot.test.ts index 26f428b160e..72fbc6223a6 100644 --- a/src/renderer/src/components/AgentStateDot.test.ts +++ b/src/renderer/src/components/AgentStateDot.test.ts @@ -51,12 +51,10 @@ describe('AgentStateDot', () => { (state) => { const markup = renderMarkup(state) - // Why: the "needs you" states share the dashboard's MessageCircleQuestion - // glyph rather than a bare dot. Assert the lucide class hook + amber text - // color without coupling to the exact SVG path lucide emits. - expect(markup).toContain('lucide-message-circle-question') + expect(markup).toContain('lucide-message-circle-question-mark') expect(markup).toContain('text-amber-500') expect(markup).not.toContain('bg-amber-500') + expect(markup).not.toContain('data-agent-spinner') } ) diff --git a/src/renderer/src/components/AgentStateDot.tsx b/src/renderer/src/components/AgentStateDot.tsx index 19362504129..97d919dca65 100644 --- a/src/renderer/src/components/AgentStateDot.tsx +++ b/src/renderer/src/components/AgentStateDot.tsx @@ -96,11 +96,6 @@ export const AgentStateDot = React.memo(function AgentStateDot({ } if (state === 'permission' || state === 'waiting') { - // Why: the "needs you" states (an agent asking a question / awaiting - // input) render the same MessageCircleQuestion glyph the dashboard card - // uses for its pending-question pill, so "an agent needs you" reads - // identically across the board, sidebar rows, and terminal tabs instead - // of as an ambiguous amber dot. Amber still carries the attention color. return ( { expect(screen.queryByText(/\d+d/)).not.toBeInTheDocument() }) + it('shows the question glyph once when a summary is available', () => { + const attentionCard = card({ + bucket: 'attention', + dotState: 'waiting', + askSummary: 'Approve deploy?' + }) + const { container, rerender } = render( + + ) + + expect(screen.queryByTestId('state-dot')).not.toBeInTheDocument() + expect(container.querySelectorAll('.lucide-message-circle-question-mark')).toHaveLength(1) + + rerender( + + ) + expect(screen.getByTestId('state-dot')).toBeInTheDocument() + expect(container.querySelector('.lucide-message-circle-question-mark')).toBeNull() + }) + it('skips structured-clone rerenders until visible card data or its age changes', () => { const onOpenTerminal = vi.fn() const initial = card({ startedAt: 1_000 }) diff --git a/src/renderer/src/components/dashboard-popout/AgentKanbanCard.tsx b/src/renderer/src/components/dashboard-popout/AgentKanbanCard.tsx index f800f1dbe9e..e92dbcb0640 100644 --- a/src/renderer/src/components/dashboard-popout/AgentKanbanCard.tsx +++ b/src/renderer/src/components/dashboard-popout/AgentKanbanCard.tsx @@ -98,9 +98,7 @@ export const AgentKanbanCard = memo( > {card.worktreeName} - {/* Why: a pending question already renders the amber - MessageCircleQuestion glyph in the summary pill below, so suppress - the header state glyph here to avoid showing "needs you" twice. */} + {/* The summary pill already carries the attention glyph. */} {card.askSummary ? null : } diff --git a/src/renderer/src/components/dashboard/DashboardAgentRow.test.tsx b/src/renderer/src/components/dashboard/DashboardAgentRow.test.tsx index cc78ededaf2..93238b8fc56 100644 --- a/src/renderer/src/components/dashboard/DashboardAgentRow.test.tsx +++ b/src/renderer/src/components/dashboard/DashboardAgentRow.test.tsx @@ -252,9 +252,7 @@ describe('DashboardAgentRow', () => { const tokens = classTokens(markup) expect(markup).toContain('aria-label="Waiting for input"') - // Why: the "needs you" states render the amber MessageCircleQuestion glyph - // rather than a bare amber dot (unified across the board/sidebar). - expect(markup).toContain('lucide-message-circle-question') + expect(markup).toContain('lucide-message-circle-question-mark') expect(tokens).toContain('text-amber-500') expect(tokens).not.toContain('bg-red-500') }) diff --git a/src/renderer/src/components/sidebar/SidebarNav.test.tsx b/src/renderer/src/components/sidebar/SidebarNav.test.tsx index a436942bdd3..42e7ee20f82 100644 --- a/src/renderer/src/components/sidebar/SidebarNav.test.tsx +++ b/src/renderer/src/components/sidebar/SidebarNav.test.tsx @@ -20,6 +20,7 @@ const mocks = vi.hoisted(() => ({ refreshPreflightStatus: vi.fn(), checkLinearConnection: vi.fn(), hasPairedMobileDevice: false, + agentBucketCounts: { attention: 0, working: 0, idle: 0 }, dismissMobileOnboardingBadge: vi.fn(), setSetupGuideSidebarDismissed: vi.fn() })) @@ -39,6 +40,10 @@ vi.mock('@/components/activity/useActivityUnreadCount', () => ({ useActivityUnreadCount: () => 0 })) +vi.mock('@/components/dashboard/useAgentBucketCounts', () => ({ + useAgentBucketCounts: () => mocks.agentBucketCounts +})) + vi.mock('@/hooks/useShortcutLabel', () => ({ useShortcutKeyComboDetails: () => [{ keys: ['โŒ˜', 'J'], doubleTap: false }] })) @@ -203,6 +208,7 @@ describe('SidebarNav', () => { vi.clearAllMocks() await i18n.changeLanguage('en') mocks.hasPairedMobileDevice = false + mocks.agentBucketCounts = { attention: 0, working: 0, idle: 0 } setSidebarState() }) @@ -252,6 +258,26 @@ describe('SidebarNav', () => { expect(queryButtonByText(container, 'Agent Dashboard')).not.toBeNull() }) + it('uses a question glyph only for the Needs You count', async () => { + mocks.agentBucketCounts = { attention: 2, working: 3, idle: 4 } + setSidebarState({ + settings: { + ...getDefaultSettings('/tmp'), + experimentalAgentDashboardPopout: true + } + }) + const container = await renderSidebarNav() + + const attention = container.querySelector('[aria-label="Needs You: 2"]') + const working = container.querySelector('[aria-label="Working: 3"]') + const idle = container.querySelector('[aria-label="Idle: 4"]') + expect(attention?.querySelector('.lucide-message-circle-question-mark')).not.toBeNull() + expect(working?.querySelector('.rounded-full')).not.toBeNull() + expect(idle?.querySelector('.rounded-full')).not.toBeNull() + expect(working?.querySelector('svg')).toBeNull() + expect(idle?.querySelector('svg')).toBeNull() + }) + it('shows the Mobile entry by default for older settings', () => { expect(shouldShowMobileButton(null)).toBe(true) expect(shouldShowMobileButton({})).toBe(true) diff --git a/src/renderer/src/components/sidebar/SidebarNav.tsx b/src/renderer/src/components/sidebar/SidebarNav.tsx index 8a30b9db052..ccd04bf73b8 100644 --- a/src/renderer/src/components/sidebar/SidebarNav.tsx +++ b/src/renderer/src/components/sidebar/SidebarNav.tsx @@ -52,10 +52,6 @@ export function shouldShowAutomationsButton( return settings?.showAutomationsButton !== false } -// Per-state markers mirror AgentStateDot so the sidebar counts read the same as -// the board: 'attention' (needs you) renders the amber MessageCircleQuestion -// glyph โ€” the same one the board and status dots now use โ€” while the calmer -// buckets stay dots (yellow = working, neutral = idle). const DASHBOARD_BUCKET_DOT_CLASS: Record<'working' | 'idle', string> = { working: 'bg-yellow-500', idle: 'bg-neutral-500/50' diff --git a/src/renderer/src/components/sidebar/StatusIndicator.test.ts b/src/renderer/src/components/sidebar/StatusIndicator.test.ts index 274b010032e..871687ccbe1 100644 --- a/src/renderer/src/components/sidebar/StatusIndicator.test.ts +++ b/src/renderer/src/components/sidebar/StatusIndicator.test.ts @@ -35,12 +35,10 @@ describe('StatusIndicator', () => { it('renders permission as an amber question glyph', () => { const markup = renderMarkup('permission') - // Why: the "needs permission / needs you" state renders the dashboard's - // MessageCircleQuestion glyph rather than a bare amber dot. Assert the - // lucide class hook + amber text color without coupling to the SVG path. - expect(markup).toContain('lucide-message-circle-question') + expect(markup).toContain('lucide-message-circle-question-mark') expect(markup).toContain('text-amber-500') expect(markup).not.toContain('bg-amber-500') + expect(markup).not.toContain('data-agent-spinner') }) it('renders active as full emerald dot', () => { diff --git a/src/renderer/src/components/sidebar/StatusIndicator.tsx b/src/renderer/src/components/sidebar/StatusIndicator.tsx index d7bb8333177..7fffc9f9787 100644 --- a/src/renderer/src/components/sidebar/StatusIndicator.tsx +++ b/src/renderer/src/components/sidebar/StatusIndicator.tsx @@ -41,10 +41,6 @@ const StatusIndicator = React.memo(function StatusIndicator({ } if (status === 'permission') { - // Why: the "needs permission / needs you" state renders the same - // MessageCircleQuestion glyph the agent dashboard uses, so the sidebar's - // worktree-level status matches the board and agent-row indicators - // instead of showing a bare amber dot. Amber keeps the attention color. return ( { ) expect(markup).toContain('Needs permission ยท Unread') - // Why: the permission status renders the amber MessageCircleQuestion glyph - // rather than a bare amber dot. - expect(markup).toContain('lucide-message-circle-question') + expect(markup).toContain('lucide-message-circle-question-mark') expect(markup).toContain('text-amber-500') expect(markup).not.toContain('data-worktree-status-lane-unread=""') expect(markup).not.toContain('data-worktree-unread-alert=""') @@ -381,7 +379,7 @@ describe('WorktreeCardStatusSlot', () => { ) expect(markup).toContain('Needs permission') - expect(markup).toContain('lucide-message-circle-question') + expect(markup).toContain('lucide-message-circle-question-mark') expect(markup).toContain('text-amber-500') expect(markup).not.toContain('PR checks: Failed') }) diff --git a/src/renderer/src/components/tab-bar/TerminalTabLeadingIcon.test.tsx b/src/renderer/src/components/tab-bar/TerminalTabLeadingIcon.test.tsx index f0589119965..6c65ba56638 100644 --- a/src/renderer/src/components/tab-bar/TerminalTabLeadingIcon.test.tsx +++ b/src/renderer/src/components/tab-bar/TerminalTabLeadingIcon.test.tsx @@ -40,9 +40,7 @@ describe('TerminalTabLeadingIcon', () => { const markup = renderStatus('permission') expect(markup).toContain('data-agent-activity-status="permission"') - // Why: the "needs you" states render the amber MessageCircleQuestion glyph - // rather than a bare amber dot (unified across board/sidebar/tabs). - expect(markup).toContain('lucide-message-circle-question') + expect(markup).toContain('lucide-message-circle-question-mark') expect(markup).toContain('text-amber-500') expect(markup).not.toContain('bg-red-500') })