import type { IssueBlockerAttention, IssueRelationIssueSummary, SuccessfulRunHandoffState } from "@paperclipai/shared"; import { AlertTriangle } from "lucide-react"; import { Link } from "@/lib/router"; import { createIssueDetailPath } from "../lib/issueDetailBreadcrumb"; import { IssueLinkQuicklook } from "./IssueLinkQuicklook"; export function IssueBlockedNotice({ issueStatus, blockers, blockerAttention, successfulRunHandoff, agentName, }: { issueStatus?: string; blockers: IssueRelationIssueSummary[]; blockerAttention?: IssueBlockerAttention | null; successfulRunHandoff?: SuccessfulRunHandoffState | null; agentName?: string | null; }) { if (issueStatus === "done" || issueStatus === "cancelled") return null; const showSuccessfulRunHandoff = successfulRunHandoff?.required === true; if (!showSuccessfulRunHandoff && blockers.length === 0 && issueStatus !== "blocked") return null; const blockerLabel = blockers.length === 1 ? "the linked issue" : "the linked issues"; const terminalBlockers = blockers .flatMap((blocker) => blocker.terminalBlockers ?? []) .filter((blocker, index, all) => all.findIndex((candidate) => candidate.id === blocker.id) === index); const isStalled = blockerAttention?.state === "stalled"; const stalledLeafIdentifier = blockerAttention?.sampleStalledBlockerIdentifier ?? blockerAttention?.sampleBlockerIdentifier ?? null; const stalledLeafBlockers = (() => { const candidates: IssueRelationIssueSummary[] = []; for (const blocker of [...blockers, ...terminalBlockers]) { if (blocker.status !== "in_review") continue; if (candidates.some((existing) => existing.id === blocker.id)) continue; candidates.push(blocker); } if (stalledLeafIdentifier) { const preferred = candidates.find( (blocker) => (blocker.identifier ?? blocker.id) === stalledLeafIdentifier, ); if (preferred) { return [preferred, ...candidates.filter((blocker) => blocker.id !== preferred.id)]; } } return candidates; })(); const showStalledRow = isStalled && stalledLeafBlockers.length > 0; const renderBlockerChip = (blocker: IssueRelationIssueSummary) => { const issuePathId = blocker.identifier ?? blocker.id; return ( {blocker.identifier ?? blocker.id.slice(0, 8)} {blocker.title} ); }; return (
{showSuccessfulRunHandoff ? ( <>

This issue still needs a next step.

A run finished successfully, but this issue is still open in{" "} in_progress {" "} with no clear owner for the next action.

  • Mark it done or cancelled.
  • Send it for review or ask for input.
  • Mark it blocked with a blocker owner.
  • Delegate follow-up work or queue a continuation.
{successfulRunHandoff.sourceRunId && successfulRunHandoff.assigneeAgentId ? ( run {successfulRunHandoff.sourceRunId.slice(0, 8)} ) : successfulRunHandoff.sourceRunId ? ( run {successfulRunHandoff.sourceRunId.slice(0, 8)} ) : null} Corrective wake queued for {agentName ?? "the assignee"}
{successfulRunHandoff.detectedProgressSummary ? (

Detected progress: {successfulRunHandoff.detectedProgressSummary}

) : null} ) : null} {showSuccessfulRunHandoff && (blockers.length > 0 || issueStatus === "blocked") ? (
) : null} {blockers.length > 0 || issueStatus === "blocked" ? ( <>

{blockers.length > 0 ? isStalled ? stalledLeafBlockers.length > 1 ? <>Work on this issue is blocked by {blockerLabel}, but the chain is stalled in review without a clear next step. Resolve the stalled reviews below or remove them as blockers. : <>Work on this issue is blocked by {blockerLabel}, but the chain is stalled in review without a clear next step. Resolve the stalled review below or remove it as a blocker. : <>Work on this issue is blocked by {blockerLabel} until {blockers.length === 1 ? "it is" : "they are"} complete. Comments still wake the assignee for questions or triage. : <>Work on this issue is blocked until it is moved back to todo. Comments still wake the assignee for questions or triage.}

{blockers.length > 0 ? (
{blockers.map(renderBlockerChip)}
) : null} {showStalledRow ? (
Stalled in review {stalledLeafBlockers.map(renderBlockerChip)}
) : terminalBlockers.length > 0 ? (
Ultimately waiting on {terminalBlockers.map(renderBlockerChip)}
) : null} ) : null}
); }