diff --git a/apps/web/src/components/radar/types.ts b/apps/web/src/components/radar/types.ts
index 67ea1ac..e7dd307 100644
--- a/apps/web/src/components/radar/types.ts
+++ b/apps/web/src/components/radar/types.ts
@@ -3,4 +3,5 @@ export interface DrilldownDialogProps {
onOpenChange: (open: boolean) => void
agentId: string
agentName: string
+ isAgentRunning?: boolean
}
diff --git a/apps/web/src/routes/__tests__/radar.test.tsx b/apps/web/src/routes/__tests__/radar.test.tsx
index 5472ff7..01f110e 100644
--- a/apps/web/src/routes/__tests__/radar.test.tsx
+++ b/apps/web/src/routes/__tests__/radar.test.tsx
@@ -3,6 +3,39 @@ import '@testing-library/jest-dom/vitest'
import { render, screen, fireEvent, within } from '@testing-library/react'
import { vi, describe, it, expect, beforeEach } from 'vitest'
+vi.mock('@/components/radar/CompactionEventsDialog', () => ({
+ CompactionEventsDialog: ({ open, agentId, agentName, isAgentRunning, onOpenChange }: any) =>
+ open ? (
+
+
+
+ ) : null,
+}))
+vi.mock('@/components/radar/SubagentSpawnsDialog', () => ({
+ SubagentSpawnsDialog: ({ open, agentId, agentName, isAgentRunning, onOpenChange }: any) =>
+ open ? (
+
+
+
+ ) : null,
+}))
+vi.mock('@/components/radar/QuestionsAskedDialog', () => ({
+ QuestionsAskedDialog: ({ open, agentId, agentName, isAgentRunning, onOpenChange }: any) =>
+ open ? (
+
+
+
+ ) : null,
+}))
+vi.mock('@/components/radar/InterAgentMessagesDialog', () => ({
+ InterAgentMessagesDialog: ({ open, agentId, agentName, isAgentRunning, onOpenChange }: any) =>
+ open ? (
+
+
+
+ ) : null,
+}))
+
type AgentRadarRow = {
id: string
name: string
@@ -277,6 +310,125 @@ describe('RadarPage', () => {
const skeletons = document.querySelectorAll('.animate-pulse')
expect(skeletons.length).toBeGreaterThanOrEqual(5)
})
+
+ describe('dialog integration', () => {
+ const alphaAgent = {
+ id: 'agent-alpha-id',
+ name: 'agent-alpha',
+ mode: 'execute',
+ status: 'running',
+ initiativeId: null,
+ initiativeName: null,
+ taskId: null,
+ taskName: null,
+ createdAt: new Date(Date.now() - 3600_000).toISOString(),
+ questionsCount: 3,
+ messagesCount: 2,
+ subagentsCount: 1,
+ compactionsCount: 4,
+ }
+ const betaAgent = {
+ id: 'agent-beta-id',
+ name: 'agent-beta',
+ mode: 'execute',
+ status: 'stopped',
+ initiativeId: null,
+ initiativeName: null,
+ taskId: null,
+ taskName: null,
+ createdAt: new Date(Date.now() - 7200_000).toISOString(),
+ questionsCount: 0,
+ messagesCount: 0,
+ subagentsCount: 0,
+ compactionsCount: 0,
+ }
+
+ beforeEach(() => {
+ mockListForRadarUseQuery.mockReturnValue({ data: [alphaAgent, betaAgent], isLoading: false })
+ })
+
+ it('clicking a non-zero Compactions cell opens CompactionEventsDialog with correct agentId and agentName', async () => {
+ render()
+ const cell = screen.getByTestId(`cell-compactions-agent-alpha-id`)
+ fireEvent.click(cell)
+ const dialog = await screen.findByTestId('compaction-dialog')
+ expect(dialog).toBeInTheDocument()
+ expect(dialog).toHaveAttribute('data-agent-name', 'agent-alpha')
+ expect(dialog).toHaveAttribute('data-agent-id', 'agent-alpha-id')
+ })
+
+ it('clicking a non-zero Subagents cell opens SubagentSpawnsDialog', async () => {
+ render()
+ const cell = screen.getByTestId(`cell-subagents-agent-alpha-id`)
+ fireEvent.click(cell)
+ const dialog = await screen.findByTestId('subagents-dialog')
+ expect(dialog).toBeInTheDocument()
+ expect(dialog).toHaveAttribute('data-agent-name', 'agent-alpha')
+ expect(dialog).toHaveAttribute('data-agent-id', 'agent-alpha-id')
+ })
+
+ it('clicking a non-zero Questions cell opens QuestionsAskedDialog', async () => {
+ render()
+ const cell = screen.getByTestId(`cell-questions-agent-alpha-id`)
+ fireEvent.click(cell)
+ const dialog = await screen.findByTestId('questions-dialog')
+ expect(dialog).toBeInTheDocument()
+ expect(dialog).toHaveAttribute('data-agent-name', 'agent-alpha')
+ expect(dialog).toHaveAttribute('data-agent-id', 'agent-alpha-id')
+ })
+
+ it('clicking a non-zero Messages cell opens InterAgentMessagesDialog', async () => {
+ render()
+ const cell = screen.getByTestId(`cell-messages-agent-alpha-id`)
+ fireEvent.click(cell)
+ const dialog = await screen.findByTestId('messages-dialog')
+ expect(dialog).toBeInTheDocument()
+ expect(dialog).toHaveAttribute('data-agent-name', 'agent-alpha')
+ expect(dialog).toHaveAttribute('data-agent-id', 'agent-alpha-id')
+ })
+
+ it('dialog closes when onOpenChange(false) fires', async () => {
+ render()
+ const cell = screen.getByTestId(`cell-compactions-agent-alpha-id`)
+ fireEvent.click(cell)
+ const dialog = await screen.findByTestId('compaction-dialog')
+ expect(dialog).toBeInTheDocument()
+ fireEvent.click(screen.getByRole('button', { name: 'close' }))
+ expect(screen.queryByTestId('compaction-dialog')).not.toBeInTheDocument()
+ })
+
+ it('zero metric cells do not open any dialog when clicked', () => {
+ render()
+ // agent-beta has all zeros — click each zero metric cell
+ fireEvent.click(screen.getByTestId('cell-compactions-agent-beta-id'))
+ fireEvent.click(screen.getByTestId('cell-subagents-agent-beta-id'))
+ fireEvent.click(screen.getByTestId('cell-questions-agent-beta-id'))
+ fireEvent.click(screen.getByTestId('cell-messages-agent-beta-id'))
+ expect(screen.queryByTestId('compaction-dialog')).not.toBeInTheDocument()
+ expect(screen.queryByTestId('subagents-dialog')).not.toBeInTheDocument()
+ expect(screen.queryByTestId('questions-dialog')).not.toBeInTheDocument()
+ expect(screen.queryByTestId('messages-dialog')).not.toBeInTheDocument()
+ })
+
+ it('passes isAgentRunning=true when the agent status is "running"', async () => {
+ render()
+ const cell = screen.getByTestId(`cell-compactions-agent-alpha-id`)
+ fireEvent.click(cell)
+ const dialog = await screen.findByTestId('compaction-dialog')
+ expect(dialog).toHaveAttribute('data-is-running', 'true')
+ })
+
+ it('passes isAgentRunning=false when the agent status is "stopped"', async () => {
+ // Use a stopped agent with non-zero compactions
+ const stoppedAgent = { ...betaAgent, id: 'stopped-agent-id', name: 'stopped-agent', compactionsCount: 2, status: 'stopped' }
+ mockListForRadarUseQuery.mockReturnValue({ data: [stoppedAgent], isLoading: false })
+ render()
+ const cell = screen.getByTestId('cell-compactions-stopped-agent-id')
+ fireEvent.click(cell)
+ const dialog = await screen.findByTestId('compaction-dialog')
+ expect(dialog).toHaveAttribute('data-is-running', 'false')
+ })
+ })
})
describe('AppLayout - Radar nav item', () => {
diff --git a/apps/web/src/routes/radar.tsx b/apps/web/src/routes/radar.tsx
index 8547b6c..7e68b3b 100644
--- a/apps/web/src/routes/radar.tsx
+++ b/apps/web/src/routes/radar.tsx
@@ -4,6 +4,10 @@ import { trpc } from '@/lib/trpc'
import { useLiveUpdates } from '@/hooks'
import type { LiveUpdateRule } from '@/hooks'
import { Card, CardContent } from '@/components/ui/card'
+import { CompactionEventsDialog } from '@/components/radar/CompactionEventsDialog'
+import { SubagentSpawnsDialog } from '@/components/radar/SubagentSpawnsDialog'
+import { QuestionsAskedDialog } from '@/components/radar/QuestionsAskedDialog'
+import { InterAgentMessagesDialog } from '@/components/radar/InterAgentMessagesDialog'
type TimeRange = '1h' | '6h' | '24h' | '7d' | 'all'
type StatusFilter = 'all' | 'running' | 'completed' | 'crashed'
@@ -74,6 +78,14 @@ export function RadarPage() {
const { data: initiatives = [] } = trpc.listInitiatives.useQuery()
+ type DrilldownType = 'questions' | 'messages' | 'subagents' | 'compactions'
+
+ const [drilldown, setDrilldown] = useState<{
+ type: DrilldownType
+ agentId: string
+ agentName: string
+ } | null>(null)
+
const [sortState, setSortState] = useState<{ column: SortColumn; direction: 'asc' | 'desc' }>({
column: 'started',
direction: 'desc',
@@ -156,21 +168,9 @@ export function RadarPage() {
)
}
- function MetricCell({ value }: { value: number }) {
- if (value > 0) {
- return (
- {
- // TODO: open drilldown dialog — wired in Phase 4 (Dialog Integration)
- }}
- >
- {value}
- |
- )
- }
- return 0 |
- }
+ const isAgentRunning = drilldown
+ ? agents.find((a) => a.id === drilldown.agentId)?.status === 'running'
+ : false
return (
@@ -313,15 +313,76 @@ export function RadarPage() {
{new Date(agent.createdAt).toLocaleString()}
|
-
-
-
-
+ 0 ? 'cursor-pointer hover:bg-muted/50 text-right px-3 py-2' : 'text-muted-foreground text-right px-3 py-2'}
+ onClick={agent.questionsCount > 0
+ ? () => setDrilldown({ type: 'questions', agentId: agent.id, agentName: agent.name })
+ : undefined}
+ >
+ {agent.questionsCount}
+ |
+ 0 ? 'cursor-pointer hover:bg-muted/50 text-right px-3 py-2' : 'text-muted-foreground text-right px-3 py-2'}
+ onClick={agent.messagesCount > 0
+ ? () => setDrilldown({ type: 'messages', agentId: agent.id, agentName: agent.name })
+ : undefined}
+ >
+ {agent.messagesCount}
+ |
+ 0 ? 'cursor-pointer hover:bg-muted/50 text-right px-3 py-2' : 'text-muted-foreground text-right px-3 py-2'}
+ onClick={agent.subagentsCount > 0
+ ? () => setDrilldown({ type: 'subagents', agentId: agent.id, agentName: agent.name })
+ : undefined}
+ >
+ {agent.subagentsCount}
+ |
+ 0 ? 'cursor-pointer hover:bg-muted/50 text-right px-3 py-2' : 'text-muted-foreground text-right px-3 py-2'}
+ onClick={agent.compactionsCount > 0
+ ? () => setDrilldown({ type: 'compactions', agentId: agent.id, agentName: agent.name })
+ : undefined}
+ >
+ {agent.compactionsCount}
+ |
))}
)}
+
+ { if (!open) setDrilldown(null) }}
+ agentId={drilldown?.agentId ?? ''}
+ agentName={drilldown?.agentName ?? ''}
+ isAgentRunning={isAgentRunning}
+ />
+ { if (!open) setDrilldown(null) }}
+ agentId={drilldown?.agentId ?? ''}
+ agentName={drilldown?.agentName ?? ''}
+ isAgentRunning={isAgentRunning}
+ />
+ { if (!open) setDrilldown(null) }}
+ agentId={drilldown?.agentId ?? ''}
+ agentName={drilldown?.agentName ?? ''}
+ isAgentRunning={isAgentRunning}
+ />
+ { if (!open) setDrilldown(null) }}
+ agentId={drilldown?.agentId ?? ''}
+ agentName={drilldown?.agentName ?? ''}
+ isAgentRunning={isAgentRunning}
+ />
)
}