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')
})