From 8b5cfb2613dfd3aca37dcaea68f70cd48a5fcd99 Mon Sep 17 00:00:00 2001 From: Dotta Date: Mon, 27 Apr 2026 19:45:07 -0500 Subject: [PATCH] Collapse extra sub-issue blocker chips Co-Authored-By: Paperclip --- ui/src/components/IssuesList.test.tsx | 90 +++++++++++++++++++++++++++ ui/src/components/IssuesList.tsx | 66 +++++++++++--------- 2 files changed, 128 insertions(+), 28 deletions(-) diff --git a/ui/src/components/IssuesList.test.tsx b/ui/src/components/IssuesList.test.tsx index ba615dc2b2d..90162fdec0b 100644 --- a/ui/src/components/IssuesList.test.tsx +++ b/ui/src/components/IssuesList.test.tsx @@ -507,6 +507,96 @@ describe("IssuesList", () => { }); }); + it("collapses multiple workflow blocker chips to the first blocker and a count", async () => { + const issueDone = createIssue({ + id: "issue-done", + identifier: "PAP-1", + title: "Done first", + status: "done", + createdAt: new Date("2026-04-01T00:00:00.000Z"), + }); + const firstBlocker = createIssue({ + id: "issue-first-blocker", + identifier: "PAP-2", + title: "First blocker", + status: "todo", + createdAt: new Date("2026-04-02T00:00:00.000Z"), + }); + const secondBlocker = createIssue({ + id: "issue-second-blocker", + identifier: "PAP-3", + title: "Second blocker", + status: "todo", + createdAt: new Date("2026-04-03T00:00:00.000Z"), + }); + const thirdBlocker = createIssue({ + id: "issue-third-blocker", + identifier: "PAP-4", + title: "Third blocker", + status: "todo", + createdAt: new Date("2026-04-04T00:00:00.000Z"), + }); + const issueBlocked = createIssue({ + id: "issue-blocked", + identifier: "PAP-5", + title: "Blocked issue", + status: "blocked", + blockedBy: [ + { + id: "issue-first-blocker", + identifier: "PAP-2", + title: "First blocker", + status: "todo", + priority: "medium", + assigneeAgentId: null, + assigneeUserId: null, + }, + { + id: "issue-second-blocker", + identifier: "PAP-3", + title: "Second blocker", + status: "todo", + priority: "medium", + assigneeAgentId: null, + assigneeUserId: null, + }, + { + id: "issue-third-blocker", + identifier: "PAP-4", + title: "Third blocker", + status: "todo", + priority: "medium", + assigneeAgentId: null, + assigneeUserId: null, + }, + ], + createdAt: new Date("2026-04-05T00:00:00.000Z"), + }); + + const { root } = renderWithQueryClient( + undefined} + />, + container, + ); + + await waitForAssertion(() => { + expect(container.textContent).toContain("blocked by PAP-2"); + expect(container.textContent).toContain("... and 2 more"); + expect(container.textContent).not.toContain("blocked by PAP-3"); + expect(container.textContent).not.toContain("blocked by PAP-4"); + }); + + act(() => { + root.unmount(); + }); + }); + it("uses hierarchical checklist step numbers when nested rows render inline", async () => { const firstRoot = createIssue({ id: "issue-first-root", diff --git a/ui/src/components/IssuesList.tsx b/ui/src/components/IssuesList.tsx index cce4e502738..7f2e88f4b65 100644 --- a/ui/src/components/IssuesList.tsx +++ b/ui/src/components/IssuesList.tsx @@ -1303,35 +1303,45 @@ export function IssuesList({ const doneRowTitleClass = checklistMeta && issue.status === "done" ? "text-muted-foreground" : undefined; - const checklistDependencyChips = checklistMeta && unresolvedVisibleBlockers.length > 0 ? ( + const visibleBlockerChips = unresolvedVisibleBlockers + .map((blockerId) => { + const blockerIssue = issueById.get(blockerId); + if (!blockerIssue) return null; + const label = blockerIssue.identifier ?? blockerIssue.id.slice(0, 8); + const blockerStep = checklistMeta?.stepNumberByIssueId.get(blockerId); + const blockerStepSuffix = blockerStep ? ` \u00b7 step ${blockerStep}` : ""; + return { blockerId, chipLabel: `blocked by ${label}${blockerStepSuffix}` }; + }) + .filter((chip): chip is { blockerId: string; chipLabel: string } => chip !== null); + const firstVisibleBlockerChip = visibleBlockerChips[0] ?? null; + const additionalVisibleBlockerCount = Math.max(visibleBlockerChips.length - 1, 0); + const checklistDependencyChips = checklistMeta && firstVisibleBlockerChip ? ( <> - {unresolvedVisibleBlockers.map((blockerId) => { - const blockerIssue = issueById.get(blockerId); - if (!blockerIssue) return null; - const label = blockerIssue.identifier ?? blockerIssue.id.slice(0, 8); - const blockerStep = checklistMeta.stepNumberByIssueId.get(blockerId); - const blockerStepSuffix = blockerStep ? ` \u00b7 step ${blockerStep}` : ""; - const chipLabel = `blocked by ${label}${blockerStepSuffix}`; - return ( - - ); - })} + + {additionalVisibleBlockerCount > 0 ? ( + chip.chipLabel).join(", ")} + > + ... and {additionalVisibleBlockerCount} more + + ) : null} ) : null;