import { useCallback, useEffect, useState } from "react" import type { PlanComment, PlanData } from "@/lib/plan" import { addPlanComment, approvePlan, deletePlanComment, getPlanComments, rejectPlan, } from "@/lib/plan" import { Button } from "@/components/ui/button" import { Markdown } from "@/components/agents/ported" import { useResolvedTheme } from "@/lib/theme" const POLL_MS = 4000 export function PlanReview({ plan }: { plan: PlanData }) { const resolvedTheme = useResolvedTheme() const [comments, setComments] = useState>([]) const [draft, setDraft] = useState("") const [posting, setPosting] = useState(false) const [decision, setDecision] = useState(null) const [busy, setBusy] = useState<"approve" | "reject" | null>(null) const [error, setError] = useState(null) // Poll so reviewers see each other's comments without a realtime transport. useEffect(() => { let cancelled = false const load = async () => { try { const next = await getPlanComments(plan.threadId) if (!cancelled) setComments(next) } catch { /* transient; next tick retries */ } } void load() const timer = setInterval(load, POLL_MS) return () => { cancelled = true clearInterval(timer) } }, [plan.threadId]) const submitComment = useCallback(async () => { const body = draft.trim() if (!body) return setPosting(true) setError(null) try { const created = await addPlanComment(plan.threadId, body) setComments((prev) => [...prev, created]) setDraft("") } catch (e) { setError((e as Error).message) } finally { setPosting(false) } }, [draft, plan.threadId]) const removeComment = useCallback( async (id: string) => { try { await deletePlanComment(plan.threadId, id) setComments((prev) => prev.filter((c) => c.id !== id)) } catch (e) { setError((e as Error).message) } }, [plan.threadId] ) const decide = useCallback( async (kind: "approve" | "reject") => { setBusy(kind) setError(null) try { if (kind === "approve") await approvePlan(plan.threadId) else await rejectPlan(plan.threadId) setDecision( kind === "approve" ? "Plan approved — the agent is implementing it." : "Changes requested — the agent is revising the plan." ) } catch (e) { setError((e as Error).message) } finally { setBusy(null) } }, [plan.threadId] ) return (

Implementation plan

Reviewing as {plan.user.name} {plan.isOwner ? " (owner)" : ""} · status:{" "} {plan.status}

{decision && ( {decision} )} {plan.isOwner && ( )}
{plan.markdown.trim() ? ( ) : (

The plan hasn't been written yet.

)}