fix(FN-4168): surface stalled-review reason inline on task cards

Fusion-Task-Id: FN-4168
Fusion-Task-Lineage: 781ffc66-cb1c-42b9-85e1-e5cbe0655863
This commit is contained in:
Fusion
2026-05-13 03:27:09 -07:00
committed by gsxdsm
parent 8c20a23361
commit be89dbce30
3 changed files with 29 additions and 2 deletions

View File

@@ -162,6 +162,16 @@
border: var(--btn-border-width) solid color-mix(in srgb, var(--color-warning) 30%, transparent);
}
.card-stalled-review-reason {
margin-top: var(--space-xs);
font-size: 0.6875rem;
line-height: 1.4;
color: var(--text-muted);
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.card-status-badge.paused {
background: var(--status-done-bg-deep);
color: var(--text-muted);
@@ -1183,6 +1193,10 @@
padding: 1px 6px;
}
.card-stalled-review-reason {
font-size: 0.625rem;
}
.card-mission-badge {
max-width: 80px;
overflow: hidden;

View File

@@ -467,6 +467,11 @@ function areTaskCardPropsEqual(previous: TaskCardProps, next: TaskCardProps): bo
previousTask.sourceAgentId === nextTask.sourceAgentId &&
previousTask.sourceMetadata?.issueUrl === nextTask.sourceMetadata?.issueUrl &&
previousTask.sourceMetadata?.agentName === nextTask.sourceMetadata?.agentName &&
previousTask.stalledReview?.reason === nextTask.stalledReview?.reason &&
previousTask.stalledReview?.heuristic === nextTask.stalledReview?.heuristic &&
previousTask.stalledReview?.matchCount === nextTask.stalledReview?.matchCount &&
previousTask.stalledReview?.firstMatchAt === nextTask.stalledReview?.firstMatchAt &&
previousTask.stalledReview?.lastMatchAt === nextTask.stalledReview?.lastMatchAt &&
areAttachmentsEqual(previousTask.attachments, nextTask.attachments) &&
areCommentsEqual(previousTask.comments, nextTask.comments) &&
areTaskDependenciesEqual(previousTask.dependencies, nextTask.dependencies) &&
@@ -746,6 +751,7 @@ function TaskCardComponent({
const showPriorityBadge = normalizedPriority !== DEFAULT_TASK_PRIORITY;
const isStuck = isTaskStuck(task, taskStuckTimeoutMs, lastFetchTimeMs);
const stalledReview = getStalledReviewSignal(task);
const showStalledReview = Boolean(stalledReview && task.column === "in-review" && !isPaused);
const isAwaitingApproval = task.column === "triage" && task.status === "awaiting-approval";
const isArchived = task.column === "archived";
const isAgentActive = !globalPaused && !queued && !isFailed && !isPaused && !isStuck && !isAwaitingApproval && (task.column === "in-progress" || ACTIVE_STATUSES.has(task.status as string));
@@ -1394,7 +1400,7 @@ function TaskCardComponent({
Stuck
</span>
)}
{stalledReview && task.column === "in-review" && !isPaused && (
{showStalledReview && stalledReview && (
<span
className="card-status-badge card-status-badge--in-review stalled-review"
title={stalledReview.reason}
@@ -1555,6 +1561,11 @@ function TaskCardComponent({
)}
</div>
</div>
{showStalledReview && stalledReview && (
<div className="card-stalled-review-reason" title={stalledReview.reason}>
{stalledReview.reason}
</div>
)}
{isFailed && task.error && (
<div className="card-error" title={task.error}>
<span className="card-error-icon"></span>

View File

@@ -176,7 +176,7 @@ describe("TaskCard", () => {
expect(container.querySelector(".card-status-badge")).toBeNull();
});
it("renders stalled badge with reason tooltip when stalledReview is set", () => {
it("renders stalled badge with visible reason when stalledReview is set", () => {
render(
<TaskCard
task={makeTask({
@@ -197,6 +197,7 @@ describe("TaskCard", () => {
const stalledBadge = screen.getByText("Stalled");
expect(stalledBadge.getAttribute("title")).toContain("Re-enqueued for merge 3 times");
expect(screen.getByText("Re-enqueued for merge 3 times in the last 60 minutes without leaving in-review")).toBeDefined();
});
it("does not render stalled badge when stalledReview is undefined", () => {
@@ -213,6 +214,7 @@ describe("TaskCard", () => {
);
expect(screen.queryByText("Stalled")).toBeNull();
expect(screen.queryByText(/Re-enqueued for merge/)).toBeNull();
});
it("shows paused by agent label when pausedByAgentId is set", () => {