fix: Show conflict resolution agents in HQ dashboard

getHeadquartersDashboard had no section for active conflict agents,
so initiatives with a running conflict-* agent disappeared from all
HQ sections. Add resolvingConflicts array to surface them.
This commit is contained in:
Lukas May
2026-03-06 16:39:48 +01:00
parent 02ca1d568e
commit 1e16ad82e8
8 changed files with 278 additions and 2 deletions

View File

@@ -0,0 +1,52 @@
import { useNavigate } from '@tanstack/react-router'
import { Card } from '@/components/ui/card'
import { Button } from '@/components/ui/button'
import { Badge } from '@/components/ui/badge'
import { StatusDot } from '@/components/StatusDot'
import { formatRelativeTime } from '@/lib/utils'
import type { ResolvingConflictsItem } from './types'
interface Props {
items: ResolvingConflictsItem[]
}
export function HQResolvingConflictsSection({ items }: Props) {
const navigate = useNavigate()
return (
<div className="space-y-3">
<h2 className="text-sm font-medium text-muted-foreground uppercase tracking-wide">
Resolving Conflicts
</h2>
<div className="space-y-2">
{items.map((item) => (
<Card key={item.agentId} className="p-4 flex items-center justify-between gap-4">
<div className="min-w-0 flex-1">
<div className="flex items-center gap-2 text-sm">
<StatusDot status="resolving_conflict" variant="urgent" size="sm" pulse />
<span className="font-semibold">{item.initiativeName}</span>
<Badge variant="urgent" size="xs">{item.agentStatus === 'waiting_for_input' ? 'Needs Input' : 'Running'}</Badge>
</div>
<p className="text-xs text-muted-foreground mt-1">
{item.agentName} · started {formatRelativeTime(item.since)}
</p>
</div>
<Button
variant="outline"
size="sm"
onClick={() =>
navigate({
to: '/initiatives/$id',
params: { id: item.initiativeId },
search: { tab: 'execution' },
})
}
>
View
</Button>
</Card>
))}
</div>
</div>
)
}

View File

@@ -20,6 +20,7 @@ vi.mock('@/lib/utils', () => ({
import { HQWaitingForInputSection } from './HQWaitingForInputSection'
import { HQNeedsReviewSection } from './HQNeedsReviewSection'
import { HQNeedsApprovalSection } from './HQNeedsApprovalSection'
import { HQResolvingConflictsSection } from './HQResolvingConflictsSection'
import { HQBlockedSection } from './HQBlockedSection'
import { HQEmptyState } from './HQEmptyState'
@@ -268,6 +269,77 @@ describe('HQNeedsApprovalSection', () => {
})
})
// ─── HQResolvingConflictsSection ──────────────────────────────────────────────
describe('HQResolvingConflictsSection', () => {
beforeEach(() => vi.clearAllMocks())
it('renders "Resolving Conflicts" heading', () => {
render(<HQResolvingConflictsSection items={[]} />)
expect(screen.getByText('Resolving Conflicts')).toBeInTheDocument()
})
it('shows initiative name and "Running" badge for running agent', () => {
render(
<HQResolvingConflictsSection
items={[
{
initiativeId: 'init-1',
initiativeName: 'My Initiative',
agentId: 'a1',
agentName: 'conflict-1234567890',
agentStatus: 'running',
since,
},
]}
/>
)
expect(screen.getByText('My Initiative')).toBeInTheDocument()
expect(screen.getByText('Running')).toBeInTheDocument()
})
it('shows "Needs Input" badge for waiting_for_input agent', () => {
render(
<HQResolvingConflictsSection
items={[
{
initiativeId: 'init-1',
initiativeName: 'My Initiative',
agentId: 'a1',
agentName: 'conflict-1234567890',
agentStatus: 'waiting_for_input',
since,
},
]}
/>
)
expect(screen.getByText('Needs Input')).toBeInTheDocument()
})
it('"View" CTA navigates to /initiatives/$id?tab=execution', () => {
render(
<HQResolvingConflictsSection
items={[
{
initiativeId: 'init-1',
initiativeName: 'My Initiative',
agentId: 'a1',
agentName: 'conflict-1234567890',
agentStatus: 'running',
since,
},
]}
/>
)
fireEvent.click(screen.getByRole('button', { name: /view/i }))
expect(mockNavigate).toHaveBeenCalledWith({
to: '/initiatives/$id',
params: { id: 'init-1' },
search: { tab: 'execution' },
})
})
})
// ─── HQBlockedSection ────────────────────────────────────────────────────────
describe('HQBlockedSection', () => {

View File

@@ -5,4 +5,5 @@ export type WaitingForInputItem = HQDashboard['waitingForInput'][number]
export type PendingReviewInitiativeItem = HQDashboard['pendingReviewInitiatives'][number]
export type PendingReviewPhaseItem = HQDashboard['pendingReviewPhases'][number]
export type PlanningInitiativeItem = HQDashboard['planningInitiatives'][number]
export type ResolvingConflictsItem = HQDashboard['resolvingConflicts'][number]
export type BlockedPhaseItem = HQDashboard['blockedPhases'][number]

View File

@@ -7,6 +7,7 @@ import { Button } from "@/components/ui/button";
import { HQWaitingForInputSection } from "@/components/hq/HQWaitingForInputSection";
import { HQNeedsReviewSection } from "@/components/hq/HQNeedsReviewSection";
import { HQNeedsApprovalSection } from "@/components/hq/HQNeedsApprovalSection";
import { HQResolvingConflictsSection } from "@/components/hq/HQResolvingConflictsSection";
import { HQBlockedSection } from "@/components/hq/HQBlockedSection";
import { HQEmptyState } from "@/components/hq/HQEmptyState";
@@ -74,6 +75,7 @@ export function HeadquartersPage() {
data.pendingReviewInitiatives.length > 0 ||
data.pendingReviewPhases.length > 0 ||
data.planningInitiatives.length > 0 ||
data.resolvingConflicts.length > 0 ||
data.blockedPhases.length > 0;
return (
@@ -107,6 +109,9 @@ export function HeadquartersPage() {
{data.planningInitiatives.length > 0 && (
<HQNeedsApprovalSection items={data.planningInitiatives} />
)}
{data.resolvingConflicts.length > 0 && (
<HQResolvingConflictsSection items={data.resolvingConflicts} />
)}
{data.blockedPhases.length > 0 && (
<HQBlockedSection items={data.blockedPhases} />
)}