diff --git a/ui/src/components/IssueBlockedNotice.test.tsx b/ui/src/components/IssueBlockedNotice.test.tsx index 2bf0ba84435..1f34d64226b 100644 --- a/ui/src/components/IssueBlockedNotice.test.tsx +++ b/ui/src/components/IssueBlockedNotice.test.tsx @@ -223,6 +223,43 @@ describe("IssueBlockedNotice", () => { expect(node.textContent).toBe(""); }); + it("renders live-next-step copy for blocked issues needing attention with no unresolved blockers", () => { + const node = render( + , + ); + + expect(node.textContent).toContain("This task is blocked but the system has no live next step."); + expect(node.textContent).toContain("Earlier blockers resolved, and no recovery path or scheduled retry is active."); + expect(node.textContent).not.toContain("Work on this task is blocked until it is moved back to todo."); + expect(node.querySelector('[data-blocker-attention-state="needs_attention_no_blockers"]')).not.toBeNull(); + }); + + it("keeps the moved-back-to-todo fallback for manually parked blocked issues", () => { + const node = render( + , + ); + + expect(node.textContent).toContain("Work on this task is blocked until it is moved back to todo."); + expect(node.textContent).not.toContain("This task is blocked but the system has no live next step."); + }); + it("renders a recovery indicator on a blocker chip when the blocker has an active recovery action", () => { const node = render( 0; + const showNeedsAttentionNoBlockers = + blockerAttention?.state === "needs_attention" && blockers.length === 0 && issueStatus === "blocked"; const stalledLeafIdentifier = blockerAttention?.sampleStalledBlockerIdentifier ?? blockerAttention?.sampleBlockerIdentifier ?? null; const stalledLeafBlockers = (() => { @@ -199,7 +201,9 @@ export function IssueBlockedNotice({ return (
@@ -257,15 +261,37 @@ export function IssueBlockedNotice({ ) : null} {blockers.length > 0 || issueStatus === "blocked" ? ( <> -

- {blockers.length > 0 - ? isStalled - ? stalledLeafBlockers.length > 1 - ? <>Work on this task 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 task 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 task 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 task is blocked until it is moved back to todo. Comments still wake the assignee for questions or triage.} -

+ {showNeedsAttentionNoBlockers ? ( + <> +

+ This task is blocked but the system has no live next step. +

+

+ Earlier blockers resolved, and no recovery path or scheduled retry is active. The assignee + needs to either move it back to{" "} + + todo + {" "} + so a fresh run is queued, or add a real blocker if work genuinely can't proceed. +

+ {blockerAttention.coveredBlockerCount > 0 ? ( +

+ Some ancestor blockers are still covered by active work — see the "Ultimately waiting on" row + below. +

+ ) : null} + + ) : ( +

+ {blockers.length > 0 + ? isStalled + ? stalledLeafBlockers.length > 1 + ? <>Work on this task 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 task 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 task 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 task is blocked until it is moved back to todo. Comments still wake the assignee for questions or triage.} +

+ )} {blockers.length > 0 ? (
{blockers.map(renderBlockerChip)} diff --git a/ui/src/components/StatusIcon.test.tsx b/ui/src/components/StatusIcon.test.tsx index 7ad5c09443a..6129891c874 100644 --- a/ui/src/components/StatusIcon.test.tsx +++ b/ui/src/components/StatusIcon.test.tsx @@ -77,6 +77,28 @@ describe("StatusIcon", () => { expect(html).not.toContain("border-dashed"); }); + it("labels attention-required blocked issues with no unresolved blockers as needing assignee action", () => { + const html = renderToStaticMarkup( + , + ); + + expect(html).toContain('data-blocker-attention-state="needs_attention"'); + expect(html).toContain('aria-label="Blocked · no live continuation path — assignee needs to act"'); + expect(html).toContain("border-red-600"); + }); + it("shows active covered work on mixed attention-required blockers", () => { const html = renderToStaticMarkup(
-
+