feat(FN-5123): merge fusion/fn-5123

This commit is contained in:
gsxdsm
2026-05-19 06:16:06 -07:00
parent 5ea5252549
commit b6623c6001
4 changed files with 71 additions and 17 deletions

View File

@@ -1611,16 +1611,18 @@ export function ListView({
<div className="list-empty-cell list-card-empty">No tasks</div>
) : (
columnTasks.map((task) => {
const isFailed = task.status === "failed";
const isPaused = task.paused === true;
const isDoneColumn = task.column === "done";
const visualStatus = isDoneColumn ? "done" : task.status;
const isFailed = !isDoneColumn && task.status === "failed";
const isPaused = !isDoneColumn && task.paused === true;
const isStuckState = isTaskStuck(task, taskStuckTimeoutMs, lastFetchTimeMs);
const isAgentActive =
!globalPaused &&
!isFailed &&
!isPaused &&
!isStuckState &&
(task.column === "in-progress" || ACTIVE_STATUSES.has(task.status as string));
const hasStatus = typeof task.status === "string" && task.status.trim().length > 0;
(task.column === "in-progress" || ACTIVE_STATUSES.has(visualStatus as string));
const hasStatus = typeof visualStatus === "string" && visualStatus.trim().length > 0;
const hasDependencies = Boolean(task.dependencies && task.dependencies.length > 0);
const taskProgress = getTaskProgress(task);
const hasProgress = taskProgress.hasProgress;
@@ -1668,7 +1670,7 @@ export function ListView({
<span className="list-status-badge stuck">Stuck</span>
) : hasStatus ? (
<span className={`list-status-badge list-status-badge--${task.column}${isFailed ? " failed" : ""}${isAgentActive ? " pulsing" : ""}`}>
{getTaskStatusLabel(task.status ?? "")}
{getTaskStatusLabel(visualStatus ?? "")}
</span>
) : null}
</div>
@@ -1801,15 +1803,17 @@ export function ListView({
</tr>
) : (
columnTasks.map((task) => {
const isFailed = task.status === "failed";
const isPaused = task.paused === true;
const isDoneColumn = task.column === "done";
const visualStatus = isDoneColumn ? "done" : task.status;
const isFailed = !isDoneColumn && task.status === "failed";
const isPaused = !isDoneColumn && task.paused === true;
const isStuckState = isTaskStuck(task, taskStuckTimeoutMs, lastFetchTimeMs);
const isAgentActive =
!globalPaused &&
!isFailed &&
!isPaused &&
!isStuckState &&
(task.column === "in-progress" || ACTIVE_STATUSES.has(task.status as string));
(task.column === "in-progress" || ACTIVE_STATUSES.has(visualStatus as string));
const isDragging = draggingTaskId === task.id;
return (
@@ -1869,13 +1873,13 @@ export function ListView({
<span className="list-status-badge stuck">
Stuck
</span>
) : task.status ? (
) : visualStatus ? (
<span
className={`list-status-badge list-status-badge--${task.column}${isFailed ? " failed" : ""}${
isAgentActive ? " pulsing" : ""
}`}
>
{getTaskStatusLabel(task.status ?? "")}
{getTaskStatusLabel(visualStatus ?? "")}
</span>
) : (
<span className="list-status-badge">-</span>

View File

@@ -782,9 +782,11 @@ function TaskCardComponent({
}
}, [onOpenDetail, addToast]);
const isFailed = task.status === "failed";
const isPaused = task.paused === true || task.userPaused === true;
const pausedByAgent = Boolean(task.paused && task.pausedByAgentId);
const isDoneColumn = task.column === "done";
const visualStatus = isDoneColumn ? "done" : task.status;
const isFailed = !isDoneColumn && task.status === "failed";
const isPaused = !isDoneColumn && (task.paused === true || task.userPaused === true);
const pausedByAgent = Boolean(!isDoneColumn && task.paused && task.pausedByAgentId);
const normalizedPriority = normalizeTaskPriorityValue(task.priority);
const showPriorityBadge = normalizedPriority !== DEFAULT_TASK_PRIORITY;
const isStuck = isTaskStuck(task, taskStuckTimeoutMs, lastFetchTimeMs);
@@ -803,7 +805,7 @@ function TaskCardComponent({
const taskAgeStalenessCopy = getTaskAgeStalenessCopy(task.ageStaleness);
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));
const isAgentActive = !globalPaused && !queued && !isFailed && !isPaused && !isStuck && !isAwaitingApproval && (task.column === "in-progress" || ACTIVE_STATUSES.has(visualStatus as string));
const isDraggable = !disableDrag && !queued && !isPaused && !isEditing && !isArchived; // Disable drag during edit/archived or host embedding
// Check if this card can be edited inline
@@ -1552,11 +1554,11 @@ function TaskCardComponent({
{pausedByAgent ? "paused by agent" : "paused"}
</span>
)}
{!isPaused && task.status && task.status !== "queued" && (
{!isPaused && visualStatus && visualStatus !== "queued" && (
<span
className={`card-status-badge card-status-badge--${task.column}${isAwaitingApproval ? " awaiting-approval" : ""}${ACTIVE_STATUSES.has(task.status) ? " pulsing" : ""}${isFailed ? " failed" : ""}${isStuck ? " stuck" : ""}`}
className={`card-status-badge card-status-badge--${task.column}${isAwaitingApproval ? " awaiting-approval" : ""}${ACTIVE_STATUSES.has(visualStatus) ? " pulsing" : ""}${isFailed ? " failed" : ""}${isStuck ? " stuck" : ""}`}
>
{isStuck ? "Stuck" : isAwaitingApproval ? "Awaiting Approval" : getTaskStatusLabel(task.status)}
{isStuck ? "Stuck" : isAwaitingApproval ? "Awaiting Approval" : getTaskStatusLabel(visualStatus)}
</span>
)}
{hasInReviewStall && stallCopy && (

View File

@@ -288,6 +288,30 @@ describe("ListView", () => {
matchMediaSpy.mockRestore();
});
it("keeps done status badge in table view when stale paused metadata exists", () => {
const tasks = [
createMockTask({ id: "FN-001", column: "done", status: "paused", paused: true, pausedByAgentId: "agent-1" }),
];
renderListView({ tasks });
expect(screen.queryByText("paused by agent")).toBeNull();
expect(screen.queryByText("paused")).toBeNull();
expect(screen.getByText("done")).toBeDefined();
});
it("keeps done status badge in mobile card view when stale paused metadata exists", () => {
const matchMediaSpy = mockMobileViewport();
const tasks = [
createMockTask({ id: "FN-001", column: "done", status: "paused", paused: true, pausedByAgentId: "agent-1" }),
];
renderListView({ tasks });
expect(screen.queryByText("paused by agent")).toBeNull();
expect(screen.queryByText("paused")).toBeNull();
expect(screen.getByText("done")).toBeDefined();
matchMediaSpy.mockRestore();
});
it("shows empty state when no tasks", () => {
renderListView({ tasks: [] });
expect(screen.getByText("No tasks yet")).toBeDefined();

View File

@@ -969,6 +969,30 @@ describe("TaskCard", () => {
expect(screen.queryByText("paused by agent")).toBeNull();
});
it("keeps done status badge when stale paused metadata exists", () => {
const { container } = render(
<TaskCard
task={makeTask({ column: "done", status: "paused", paused: true, userPaused: true, pausedByAgentId: "agent-1" })}
onOpenDetail={noop}
addToast={noop}
/>,
);
expect(screen.queryByText("paused by agent")).toBeNull();
expect(screen.queryByText("paused")).toBeNull();
expect(screen.getByText("done")).toBeDefined();
expect(container.querySelector(".card")?.className).not.toContain("paused");
});
it("keeps done status badge when done task status is paused", () => {
render(
<TaskCard task={makeTask({ column: "done", status: "paused", paused: false, userPaused: false })} onOpenDetail={noop} addToast={noop} />,
);
expect(screen.queryByText("paused")).toBeNull();
expect(screen.getByText("done")).toBeDefined();
});
it("renders decision-only badge when noCommitsExpected is true", () => {
render(<TaskCard task={makeTask({ noCommitsExpected: true })} onOpenDetail={noop} addToast={noop} />);
expect(screen.getByText("decision-only")).toBeTruthy();