Files
autocoder/ui/src/components/OrchestratorStatusCard.tsx
Auto 56f260cb79 feat(ui): use theme-aware colors for Maestro status card
Replace hardcoded violet/purple colors in OrchestratorStatusCard with
standard primary color CSS variables to ensure proper theming across
all theme variants (light/dark mode, Twitter, Claude, Neo Brutalism,
Aurora, Retro Arcade).

Changes:
- Card background: bg-primary/10 with border-primary/30
- Maestro title and state text: text-primary
- Activity button: text-primary hover:bg-primary/10
- Events border and timestamps: use primary color variants

Also includes:
- Enhanced review-pr command with vision alignment checks
- CLAUDE.md improvements: prerequisites, testing section, React 19 update
- Simplified parallel mode documentation

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-29 07:51:42 +02:00

160 lines
5.5 KiB
TypeScript

import { useState } from 'react'
import { ChevronDown, ChevronUp, Code, FlaskConical, Clock, Lock, Sparkles } from 'lucide-react'
import { OrchestratorAvatar } from './OrchestratorAvatar'
import type { OrchestratorStatus, OrchestratorState } from '../lib/types'
import { Card, CardContent } from '@/components/ui/card'
import { Button } from '@/components/ui/button'
import { Badge } from '@/components/ui/badge'
interface OrchestratorStatusCardProps {
status: OrchestratorStatus
}
// Get a friendly state description
function getStateText(state: OrchestratorState): string {
switch (state) {
case 'idle':
return 'Standing by...'
case 'initializing':
return 'Setting up features...'
case 'scheduling':
return 'Planning next moves...'
case 'spawning':
return 'Deploying agents...'
case 'monitoring':
return 'Watching progress...'
case 'complete':
return 'Mission accomplished!'
default:
return 'Orchestrating...'
}
}
// Get state color
function getStateColor(state: OrchestratorState): string {
switch (state) {
case 'complete':
return 'text-primary'
case 'spawning':
return 'text-primary'
case 'scheduling':
case 'monitoring':
return 'text-primary'
case 'initializing':
return 'text-yellow-600 dark:text-yellow-400'
default:
return 'text-muted-foreground'
}
}
// Format timestamp to relative time
function formatRelativeTime(timestamp: string): string {
const now = new Date()
const then = new Date(timestamp)
const diffMs = now.getTime() - then.getTime()
const diffSecs = Math.floor(diffMs / 1000)
if (diffSecs < 5) return 'just now'
if (diffSecs < 60) return `${diffSecs}s ago`
const diffMins = Math.floor(diffSecs / 60)
if (diffMins < 60) return `${diffMins}m ago`
return `${Math.floor(diffMins / 60)}h ago`
}
export function OrchestratorStatusCard({ status }: OrchestratorStatusCardProps) {
const [showEvents, setShowEvents] = useState(false)
return (
<Card className="mb-4 bg-primary/10 border-primary/30 py-4">
<CardContent className="p-4">
<div className="flex items-start gap-4">
{/* Avatar */}
<OrchestratorAvatar state={status.state} size="md" />
{/* Main content */}
<div className="flex-1 min-w-0">
{/* Header row */}
<div className="flex items-center gap-2 mb-1">
<span className="font-semibold text-lg text-primary">
Maestro
</span>
<span className={`text-sm font-medium ${getStateColor(status.state)}`}>
{getStateText(status.state)}
</span>
</div>
{/* Current message */}
<p className="text-sm text-foreground mb-3 line-clamp-2">
{status.message}
</p>
{/* Status badges row */}
<div className="flex flex-wrap items-center gap-2">
{/* Coding agents badge */}
<Badge variant="outline" className="bg-blue-100 text-blue-700 border-blue-300 dark:bg-blue-900/30 dark:text-blue-300 dark:border-blue-700">
<Code size={12} />
Coding: {status.codingAgents}
</Badge>
{/* Testing agents badge */}
<Badge variant="outline" className="bg-purple-100 text-purple-700 border-purple-300 dark:bg-purple-900/30 dark:text-purple-300 dark:border-purple-700">
<FlaskConical size={12} />
Testing: {status.testingAgents}
</Badge>
{/* Ready queue badge */}
<Badge variant="outline" className="bg-green-100 text-green-700 border-green-300 dark:bg-green-900/30 dark:text-green-300 dark:border-green-700">
<Clock size={12} />
Ready: {status.readyCount}
</Badge>
{/* Blocked badge (only show if > 0) */}
{status.blockedCount > 0 && (
<Badge variant="outline" className="bg-amber-100 text-amber-700 border-amber-300 dark:bg-amber-900/30 dark:text-amber-300 dark:border-amber-700">
<Lock size={12} />
Blocked: {status.blockedCount}
</Badge>
)}
</div>
</div>
{/* Recent events toggle */}
{status.recentEvents.length > 0 && (
<Button
variant="ghost"
size="sm"
onClick={() => setShowEvents(!showEvents)}
className="text-primary hover:bg-primary/10"
>
<Sparkles size={12} />
Activity
{showEvents ? <ChevronUp size={14} /> : <ChevronDown size={14} />}
</Button>
)}
</div>
{/* Collapsible recent events */}
{showEvents && status.recentEvents.length > 0 && (
<div className="mt-3 pt-3 border-t border-primary/20">
<div className="space-y-1.5">
{status.recentEvents.map((event, idx) => (
<div
key={`${event.timestamp}-${idx}`}
className="flex items-start gap-2 text-xs"
>
<span className="text-primary shrink-0 font-mono">
{formatRelativeTime(event.timestamp)}
</span>
<span className="text-foreground">
{event.message}
</span>
</div>
))}
</div>
</div>
)}
</CardContent>
</Card>
)
}