import "./TaskReviewTab.css"; import type { Task, TaskDetail } from "@fusion/core"; import { useMemo, useState } from "react"; import { refreshTaskReview, reviseTaskReviewItems } from "../api"; import type { ToastType } from "../hooks/useToast"; interface Props { task: Task | TaskDetail; projectId?: string; onTaskUpdated?: (task: Task) => void; addToast: (message: string, type?: ToastType) => void; } function formatTimestamp(value?: string): string { if (!value) return "Never"; return new Date(value).toLocaleString(); } export function TaskReviewTab({ task, projectId, onTaskUpdated, addToast }: Props) { const [selected, setSelected] = useState([]); const [refreshing, setRefreshing] = useState(false); const [revising, setRevising] = useState(false); const [error, setError] = useState(null); const review = task.reviewState; const canRevise = selected.length > 0 && !revising; const isPrMode = review?.source === "pull-request"; const summaryText = useMemo(() => { if (!review) return "No review feedback captured yet."; const decision = review.summary?.reviewDecision ?? "REVIEW_REQUIRED"; return `${decision} · ${review.items.length} review item(s)`; }, [review]); const decisionLabel = review?.summary?.reviewDecision ?? undefined; const toggleSelected = (id: string) => { setSelected((prev) => (prev.includes(id) ? prev.filter((value) => value !== id) : [...prev, id])); }; const onRefresh = async () => { try { setError(null); setRefreshing(true); const result = await refreshTaskReview(task.id, projectId); onTaskUpdated?.({ ...task, reviewState: result.reviewState } as Task); addToast("Review refreshed", "success"); } catch (refreshError) { const message = refreshError instanceof Error ? refreshError.message : "Failed to refresh review"; setError(message); addToast(message, "error"); } finally { setRefreshing(false); } }; const onRevise = async () => { try { setError(null); setRevising(true); const result = await reviseTaskReviewItems(task.id, selected, projectId); onTaskUpdated?.({ ...result.task, reviewState: result.reviewState } as Task); setSelected([]); addToast("Queued same-task revision", "success"); } catch (reviseError) { const message = reviseError instanceof Error ? reviseError.message : "Failed to queue revision"; setError(message); addToast(message, "error"); } finally { setRevising(false); } }; return (

{summaryText}

{decisionLabel ? ( {decisionLabel} ) : null}
Last refreshed: {formatTimestamp(review?.lastRefreshedAt)}
{error ?
{error}
: null} {!isPrMode ? (
GitHub PR review details are only available when auto-merge uses Pull Request mode. Reviewer-agent feedback will appear here in direct mode.
) : null} {isPrMode && review?.summary?.reviewers?.length ? (
    {review.summary.reviewers.map((reviewer) => (
  • @{reviewer.login} · {reviewer.state}
  • ))}
) : null} {isPrMode && review?.summary?.blockingReasons?.length ? (
    {review.summary.blockingReasons.map((reason) =>
  • {reason}
  • )}
) : null} {isPrMode && review?.items?.length ? (
    {review.items.map((item) => { const status = review?.addressing.find((record) => record.itemId === item.id)?.status ?? "queued"; return (
  • ); })}
) : isPrMode ? (
No review items yet.
) : null}
); }