/** * DevQALoop - Developer ↔ QA Review Loop Interface * * Visualizes the iterative review cycle between Developer and QA agents, * showing iteration count, feedback history, and current state. * * @module components/DevQALoop */ import { useState } from 'react'; import { useTeamStore } from '../store/teamStore'; import type { DevQALoop as DevQALoopType, ReviewFeedback, ReviewIssue } from '../types/team'; import { RefreshCw, CheckCircle, XCircle, AlertTriangle, Clock, MessageSquare, FileCode, Bug, Lightbulb, ChevronDown, ChevronUp, Send, ThumbsUp, ThumbsDown, AlertOctagon, } from 'lucide-react'; // === Sub-Components === interface FeedbackItemProps { feedback: ReviewFeedback; iteration: number; isExpanded: boolean; onToggle: () => void; } function FeedbackItem({ feedback, iteration, isExpanded, onToggle }: FeedbackItemProps) { const verdictColors = { approved: 'bg-green-100 text-green-700 dark:bg-green-900/30 dark:text-green-300', needs_work: 'bg-yellow-100 text-yellow-700 dark:bg-yellow-900/30 dark:text-yellow-300', rejected: 'bg-red-100 text-red-700 dark:bg-red-900/30 dark:text-red-300', }; const verdictIcons = { approved: , needs_work: , rejected: , }; const severityColors = { critical: 'bg-red-100 text-red-700 dark:bg-red-900/30 dark:text-red-300', major: 'bg-orange-100 text-orange-700 dark:bg-orange-900/30 dark:text-orange-300', minor: 'bg-yellow-100 text-yellow-700 dark:bg-yellow-900/30 dark:text-yellow-300', suggestion: 'bg-blue-100 text-blue-700 dark:bg-blue-900/30 dark:text-blue-300', }; return (
{isExpanded && (
{/* Comments */} {feedback.comments.length > 0 && (
Comments
    {feedback.comments.map((comment, idx) => (
  • {comment}
  • ))}
)} {/* Issues */} {feedback.issues.length > 0 && (
Issues ({feedback.issues.length})
{feedback.issues.map((issue, idx) => (
{issue.severity} {issue.file && ( {issue.file}{issue.line ? `:${issue.line}` : ''} )}

{issue.description}

{issue.suggestion && (

{issue.suggestion}

)}
))}
)}
)}
); } interface ReviewFormProps { loopId: string; teamId: string; onSubmit: (feedback: Omit) => void; onCancel: () => void; } function ReviewForm({ loopId: _loopId, teamId: _teamId, onSubmit, onCancel }: ReviewFormProps) { const [verdict, setVerdict] = useState('needs_work'); const [comment, setComment] = useState(''); const [issues, setIssues] = useState([]); const [newIssue, setNewIssue] = useState>({}); const handleAddIssue = () => { if (!newIssue.description) return; setIssues([...issues, { severity: newIssue.severity || 'minor', description: newIssue.description, file: newIssue.file, line: newIssue.line, suggestion: newIssue.suggestion, } as ReviewIssue]); setNewIssue({}); }; const handleSubmit = () => { onSubmit({ verdict, comments: comment ? [comment] : [], issues, }); }; return (

Submit Review

{/* Verdict */}
{(['approved', 'needs_work', 'rejected'] as const).map(v => ( ))}
{/* Comment */}