diff --git a/frontend/src/renderer/components/PRSummaryDisplay.test.tsx b/frontend/src/renderer/components/PRSummaryDisplay.test.tsx
index 373d1e43e..cc183b174 100644
--- a/frontend/src/renderer/components/PRSummaryDisplay.test.tsx
+++ b/frontend/src/renderer/components/PRSummaryDisplay.test.tsx
@@ -52,4 +52,29 @@ describe("PRSummaryParts", () => {
expect(screen.queryByText("types")).not.toBeInTheDocument();
expect(screen.getByText("+1 check")).toBeInTheDocument();
});
+
+ it("counts overflow beyond helper-truncated links", () => {
+ render(
+ ,
+ );
+
+ expect(screen.getByText("unit")).toBeInTheDocument();
+ expect(screen.getByText("lint")).toBeInTheDocument();
+ expect(screen.getByText("types")).toBeInTheDocument();
+ expect(screen.queryByText("build")).not.toBeInTheDocument();
+ expect(screen.getByText("+1 check")).toBeInTheDocument();
+ });
});
diff --git a/frontend/src/renderer/components/PRSummaryDisplay.tsx b/frontend/src/renderer/components/PRSummaryDisplay.tsx
index b9385b97b..bf45f5fc4 100644
--- a/frontend/src/renderer/components/PRSummaryDisplay.tsx
+++ b/frontend/src/renderer/components/PRSummaryDisplay.tsx
@@ -97,7 +97,10 @@ export function PRSummaryParts({
>
{parts.map((part) => {
const links = part.links.slice(0, maxLinks);
- const overflowLabel = overflowPartLabel(part.links.length - links.length, part.overflowNoun);
+ const overflowLabel = overflowPartLabel(
+ (part.linkTotal ?? part.links.length) - links.length,
+ part.overflowNoun,
+ );
return (
diff --git a/frontend/src/renderer/lib/pr-display.ts b/frontend/src/renderer/lib/pr-display.ts
index f5b4c71ea..da1861df0 100644
--- a/frontend/src/renderer/lib/pr-display.ts
+++ b/frontend/src/renderer/lib/pr-display.ts
@@ -41,6 +41,7 @@ export type PRSummaryPart = {
status: string;
summary?: string;
links: PRSummaryLink[];
+ linkTotal?: number;
overflowLabel?: string;
overflowNoun?: string;
tone: PRDisplayTone;
@@ -124,6 +125,7 @@ export function prSummaryParts(pr: SessionPRSummary): PRSummaryPart[] {
status: ciLabel(pr.ci.state),
summary: ciSummary(pr),
links: ciLinks(pr),
+ linkTotal: pr.ci.state === "failing" ? pr.ci.failingChecks.length : 0,
overflowLabel: pr.ci.state === "failing" ? overflowLabel(pr.ci.failingChecks.length, 3, "check") : undefined,
overflowNoun: "check",
tone: ciTone(pr.ci.state),
@@ -134,6 +136,7 @@ export function prSummaryParts(pr: SessionPRSummary): PRSummaryPart[] {
status: mergeabilityLabel(pr.mergeability.state),
summary: mergeSummary(pr),
links: mergeLinks(pr),
+ linkTotal: mergeLinkTotal(pr),
overflowLabel: mergeOverflowLabel(pr),
overflowNoun: mergeOverflowNoun(pr),
tone: mergeabilityTone(pr.mergeability.state),
@@ -144,6 +147,7 @@ export function prSummaryParts(pr: SessionPRSummary): PRSummaryPart[] {
status: reviewLabel(pr.review.decision),
summary: reviewSummary(pr),
links: reviewLinks(pr),
+ linkTotal: reviewLinkTotal(pr),
overflowLabel:
pr.state === "draft" || pr.review.decision === "review_required"
? undefined
@@ -254,10 +258,34 @@ function mergeOverflowLabel(pr: SessionPRSummary): string | undefined {
return undefined;
}
+function mergeLinkTotal(pr: SessionPRSummary): number {
+ if (pr.state === "merged" || pr.state === "closed") {
+ return 0;
+ }
+ if (pr.mergeability.state === "conflicting") {
+ const conflictFileCount = pr.mergeability.conflictFiles?.length ?? 0;
+ return conflictFileCount > 0 ? conflictFileCount : mergeLinks(pr).length;
+ }
+ if (pr.mergeability.state === "blocked" || pr.mergeability.state === "unstable") {
+ return pr.mergeability.reasons.length;
+ }
+ return 0;
+}
+
function mergeOverflowNoun(pr: SessionPRSummary): string {
return (pr.mergeability.conflictFiles ?? []).length > 0 ? "file" : "reason";
}
+function reviewLinkTotal(pr: SessionPRSummary): number {
+ if (pr.state === "merged" || pr.state === "closed" || pr.state === "draft") {
+ return 0;
+ }
+ if (pr.review.decision !== "changes_requested" && !pr.review.hasUnresolvedHumanComments) {
+ return 0;
+ }
+ return pr.review.unresolvedBy.length > 0 ? pr.review.unresolvedBy.length : reviewLinks(pr).length;
+}
+
function toCIState(value: string): SessionPRSummary["ci"]["state"] {
return ciStates.has(value as SessionPRSummary["ci"]["state"])
? (value as SessionPRSummary["ci"]["state"])