feat(FN-4462): complete Step 2 — render retry-exhausted badge text

Fusion-Task-Id: FN-4462
Fusion-Task-Lineage: 15fc09f5-1363-429e-b131-9c9fdabee532
This commit is contained in:
Fusion
2026-05-14 22:50:14 -07:00
committed by gsxdsm
parent 738cd54eb1
commit d4801b640f
2 changed files with 34 additions and 6 deletions

View File

@@ -27,7 +27,7 @@ import { getEndToEndDurationMs, getTimedDurationMs, getWorkflowRuntimeMs, parseT
import type { ToastType } from "../hooks/useToast";
import { useConfirm } from "../hooks/useConfirm";
import { extractDependencyDeleteConflict } from "../utils/taskDelete";
import type { BlockerFanoutEntry } from "../hooks/useBlockerFanout";
import { MAX_AUTO_MERGE_RETRIES, type BlockerFanoutEntry } from "../hooks/useBlockerFanout";
import { useRetryWarning } from "../context/RetryWarningContext";
// ── Mission title caching ───────────────────────────────────────────────────
@@ -768,7 +768,12 @@ function TaskCardComponent({
const stalledReview = getStalledReviewSignal(task);
const showStalledReview = Boolean(stalledReview && task.column === "in-review" && !isPaused);
const hasInReviewStall = shouldShowInReviewStallBadge(task);
const stallCopy = task.inReviewStall ? getInReviewStallCopy(task.inReviewStall) : undefined;
const stallCopy = task.inReviewStall
? getInReviewStallCopy(task.inReviewStall, {
mergeRetries: task.mergeRetries,
maxAutoMergeRetries: MAX_AUTO_MERGE_RETRIES,
})
: undefined;
const hasStalePausedReview = shouldShowStalePausedReviewBadge(task);
const stalePausedReviewCopy = task.stalePausedReview ? getStalePausedReviewCopy(task.stalePausedReview) : undefined;
const hasTaskAgeStaleness = shouldShowTaskAgeStalenessBadge(task);
@@ -1477,7 +1482,7 @@ function TaskCardComponent({
title={`${stallCopy.headline} — ${stallCopy.description}`}
data-stall-code={stallCopy.code}
>
{stallCopy.badgeLabel}
{stallCopy.badgeLabel}{stallCopy.counter ? ` ${stallCopy.counter}` : ""}
</span>
)}
{hasStalePausedReview && stalePausedReviewCopy && (

View File

@@ -456,12 +456,13 @@ describe("TaskCard", () => {
expect(screen.queryByText(/Re-enqueued for merge/)).toBeNull();
});
it("renders in-review stall badge with code and tooltip", () => {
it("renders retry-exhausted in-review stall badge with counter, code, and tooltip", () => {
render(
<TaskCard
task={makeTask({
column: "in-review",
status: "merging",
mergeRetries: 3,
inReviewStall: {
code: "merge-retries-exhausted",
reason: "Auto-merge retries exhausted",
@@ -473,11 +474,33 @@ describe("TaskCard", () => {
/>,
);
const badge = screen.getByText("Stall");
const badge = screen.getByText("Retries exhausted 3/3");
expect(badge.getAttribute("data-stall-code")).toBe("merge-retries-exhausted");
expect(badge.getAttribute("title")).toContain("Auto-merge retries exhausted");
});
it("renders merge-blocker in-review stall badge without retry counter", () => {
render(
<TaskCard
task={makeTask({
column: "in-review",
status: "merging",
mergeRetries: 3,
inReviewStall: {
code: "merge-blocker",
reason: "Merge blocked by pre-merge check",
observedAt: "2026-05-13T00:00:00.000Z",
},
})}
onOpenDetail={noop}
addToast={noop}
/>,
);
expect(screen.getByText("Merge blocked")).toBeDefined();
expect(screen.queryByText(/\/3/)).toBeNull();
});
it.each([
{
label: "paused in-review task",
@@ -510,7 +533,7 @@ describe("TaskCard", () => {
},
])("hides in-review stall badge for $label", ({ task }) => {
render(<TaskCard task={task} onOpenDetail={noop} addToast={noop} />);
expect(screen.queryByText("Stall")).toBeNull();
expect(screen.queryByText("Retries exhausted")).toBeNull();
});
it("renders stale paused review badge for paused in-review signal", () => {