import * as React from "react"; import Markdown from "react-markdown"; import type { AgentEvent, AgentInfo, AgentStatusValue } from "../api/agents"; import { agentsApi, subscribeAgentStream } from "../api/agents"; import type { TestCaseResult, TestResultsResponse } from "../api/client"; import { api } from "../api/client"; const { useEffect, useRef, useState } = React; const STAGE_LABELS: Record = { upcoming: "Upcoming", current: "Current", qa: "QA", merge: "To Merge", done: "Done", archived: "Archived", }; const STATUS_COLORS: Record = { running: "#3fb950", pending: "#e3b341", completed: "#aaa", failed: "#f85149", }; interface WorkItemDetailPanelProps { storyId: string; pipelineVersion: number; onClose: () => void; } function TestCaseRow({ tc }: { tc: TestCaseResult }) { const isPassing = tc.status === "pass"; return (
{isPassing ? "PASS" : "FAIL"} {tc.name}
{tc.details && (
{tc.details}
)}
); } function TestSection({ title, tests, testId, }: { title: string; tests: TestCaseResult[]; testId: string; }) { const passCount = tests.filter((t) => t.status === "pass").length; const failCount = tests.length - passCount; return (
{title} ({passCount} passed, {failCount} failed)
{tests.length === 0 ? (
No tests recorded
) : ( tests.map((tc) => ) )}
); } export function WorkItemDetailPanel({ storyId, pipelineVersion, onClose, }: WorkItemDetailPanelProps) { const [content, setContent] = useState(null); const [stage, setStage] = useState(""); const [name, setName] = useState(null); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); const [agentInfo, setAgentInfo] = useState(null); const [agentLog, setAgentLog] = useState([]); const [agentStatus, setAgentStatus] = useState(null); const [testResults, setTestResults] = useState( null, ); const panelRef = useRef(null); const cleanupRef = useRef<(() => void) | null>(null); useEffect(() => { setLoading(true); setError(null); api .getWorkItemContent(storyId) .then((data) => { setContent(data.content); setStage(data.stage); setName(data.name); }) .catch((err: unknown) => { setError(err instanceof Error ? err.message : "Failed to load content"); }) .finally(() => { setLoading(false); }); }, [storyId]); // Fetch test results on mount and when pipeline updates arrive. useEffect(() => { api .getTestResults(storyId) .then((data) => { setTestResults(data); }) .catch(() => { // Silently ignore — test results may not exist yet. }); }, [storyId, pipelineVersion]); useEffect(() => { cleanupRef.current?.(); cleanupRef.current = null; setAgentInfo(null); setAgentLog([]); setAgentStatus(null); agentsApi .listAgents() .then((agents) => { const agent = agents.find((a) => a.story_id === storyId); if (!agent) return; setAgentInfo(agent); setAgentStatus(agent.status); if (agent.status === "running" || agent.status === "pending") { const cleanup = subscribeAgentStream( storyId, agent.agent_name, (event: AgentEvent) => { switch (event.type) { case "status": setAgentStatus((event.status as AgentStatusValue) ?? null); break; case "output": setAgentLog((prev) => [...prev, event.text ?? ""]); break; case "done": setAgentStatus("completed"); break; case "error": setAgentStatus("failed"); setAgentLog((prev) => [ ...prev, `[ERROR] ${event.message ?? "Unknown error"}`, ]); break; default: break; } }, ); cleanupRef.current = cleanup; } }) .catch((err: unknown) => { console.error("Failed to load agents:", err); }); return () => { cleanupRef.current?.(); cleanupRef.current = null; }; }, [storyId]); useEffect(() => { const handleKeyDown = (e: KeyboardEvent) => { if (e.key === "Escape") { onClose(); } }; window.addEventListener("keydown", handleKeyDown); return () => window.removeEventListener("keydown", handleKeyDown); }, [onClose]); const stageLabel = STAGE_LABELS[stage] ?? stage; const hasTestResults = testResults && (testResults.unit.length > 0 || testResults.integration.length > 0); return (
{/* Header */}
{name ?? storyId}
{stage && (
{stageLabel}
)}
{/* Scrollable content area */}
{loading && (
Loading...
)} {error && (
{error}
)} {!loading && !error && content !== null && (
(

{children}

), // biome-ignore lint/suspicious/noExplicitAny: react-markdown requires any for component props h2: ({ children }: any) => (

{children}

), // biome-ignore lint/suspicious/noExplicitAny: react-markdown requires any for component props h3: ({ children }: any) => (

{children}

), }} > {content}
)} {/* Test Results section */}
Test Results
{hasTestResults ? (
) : (
No test results recorded
)}
{/* Agent Logs section */}
Agent Logs
{agentInfo && agentStatus && (
{agentInfo.agent_name} — {agentStatus}
)}
{agentInfo && agentLog.length > 0 ? (
{agentLog.join("")}
) : agentInfo ? (
{agentStatus === "running" || agentStatus === "pending" ? "Waiting for output..." : "No output."}
) : (
Coming soon
)}
{/* Placeholder sections for future content */} {( [{ id: "coverage", label: "Coverage" }] as { id: string; label: string; }[] ).map(({ id, label }) => (
{label}
Coming soon
))}
); }