feat(FN-3350): tokenize task detail danger/error styling

Fixes task detail modal danger/error styling by switching to token-based CSS variables for consistent theming across dark and light modes.

Fusion-Task-Id: FN-3350
This commit is contained in:
Fusion
2026-05-04 04:04:13 -07:00
committed by gsxdsm
parent 5be5da1938
commit 6ecfb3c4ec
24 changed files with 487 additions and 53 deletions

View File

@@ -324,7 +324,7 @@ describe("Column in-progress/in-review bulk actions", () => {
expect(screen.getByRole("menuitem", { name: /Move All to Todo/i })).toBeTruthy();
});
it.each(["in-progress", "in-review"] as const)("Stop All pauses only non-paused tasks in %s", async (column) => {
it.each(["in-progress", "in-review"] as const)("Stop All pauses only manually-pausable tasks in %s", async (column) => {
const user = userEvent.setup();
const onPauseTask = vi.fn().mockResolvedValue({} as Task);
@@ -335,7 +335,8 @@ describe("Column in-progress/in-review bulk actions", () => {
tasks={[
{ ...makeTask("FN-001"), column, paused: false },
{ ...makeTask("FN-002"), column, paused: true },
{ ...makeTask("FN-003"), column, paused: false },
{ ...makeTask("FN-003"), column, paused: false, assignedAgentId: "agent-1" },
{ ...makeTask("FN-004"), column, paused: false },
]}
onPauseTask={onPauseTask}
/>,
@@ -348,7 +349,8 @@ describe("Column in-progress/in-review bulk actions", () => {
expect(onPauseTask).toHaveBeenCalledTimes(2);
});
expect(onPauseTask).toHaveBeenCalledWith("FN-001");
expect(onPauseTask).toHaveBeenCalledWith("FN-003");
expect(onPauseTask).toHaveBeenCalledWith("FN-004");
expect(onPauseTask).not.toHaveBeenCalledWith("FN-003");
expect(screen.queryByRole("menu")).toBeNull();
expect(mockConfirm).toHaveBeenCalledWith({
title: "Stop All Tasks",
@@ -374,7 +376,7 @@ describe("Column in-progress/in-review bulk actions", () => {
expect(screen.getByText("No tasks in this column")).toBeTruthy();
});
it.each(["in-progress", "in-review"] as const)("disables Stop All when all %s tasks are already paused", async (column) => {
it.each(["in-progress", "in-review"] as const)("disables Stop All when no %s tasks are manually pausable", async (column) => {
const user = userEvent.setup();
render(
@@ -383,7 +385,7 @@ describe("Column in-progress/in-review bulk actions", () => {
column={column}
tasks={[
{ ...makeTask("FN-010"), column, paused: true },
{ ...makeTask("FN-011"), column, paused: true },
{ ...makeTask("FN-011"), column, paused: false, assignedAgentId: "agent-1" },
]}
onPauseTask={vi.fn().mockResolvedValue({} as Task)}
/>,
@@ -391,7 +393,7 @@ describe("Column in-progress/in-review bulk actions", () => {
await user.click(screen.getByRole("button", { name: `${column === "in-progress" ? "In Progress" : "In Review"} column actions` }));
expect(screen.getByRole("menuitem", { name: /Stop All/i })).toBeDisabled();
expect(screen.getByText("All tasks are already paused")).toBeTruthy();
expect(screen.getByText("No manually pausable tasks")).toBeTruthy();
});
it.each(["in-progress", "in-review"] as const)("Move All to Todo moves every task in %s", async (column) => {

View File

@@ -210,6 +210,26 @@ describe("ListView", () => {
expect(standardRow.querySelector(".list-execution-mode-badge")).toBeNull();
});
it("shows paused by agent status in table view", () => {
const tasks = [
createMockTask({ id: "FN-001", column: "in-progress", paused: true, pausedByAgentId: "agent-1" }),
];
renderListView({ tasks });
expect(screen.getByText("paused by agent")).toBeDefined();
});
it("shows paused by agent status in mobile card view", () => {
const matchMediaSpy = mockMobileViewport();
const tasks = [
createMockTask({ id: "FN-001", column: "in-progress", paused: true, pausedByAgentId: "agent-1" }),
];
renderListView({ tasks });
expect(screen.getByText("paused by agent")).toBeDefined();
matchMediaSpy.mockRestore();
});
it("shows empty state when no tasks", () => {
renderListView({ tasks: [] });
expect(screen.getByText("No tasks yet")).toBeDefined();

View File

@@ -93,6 +93,23 @@ describe("TaskCard", () => {
expect(container.querySelector(".card-status-badge")).toBeNull();
});
it("shows paused by agent label when pausedByAgentId is set", () => {
render(
<TaskCard task={makeTask({ paused: true, pausedByAgentId: "agent-1" })} onOpenDetail={noop} addToast={noop} />,
);
expect(screen.getByText("paused by agent")).toBeDefined();
});
it("shows plain paused label when pausedByAgentId is not set", () => {
render(
<TaskCard task={makeTask({ paused: true })} onOpenDetail={noop} addToast={noop} />,
);
expect(screen.getByText("paused")).toBeDefined();
expect(screen.queryByText("paused by agent")).toBeNull();
});
it("renders fast-mode indicator only when executionMode is fast", () => {
const { container, rerender } = render(
<TaskCard task={makeTask({ executionMode: "fast" })} onOpenDetail={noop} addToast={noop} />,

View File

@@ -4090,6 +4090,49 @@ describe("TaskDetailModal", () => {
expect(screen.getByRole("menuitem", { name: "Unpause" })).toBeTruthy();
});
it("hides Pause/Unpause button for agent-assigned tasks", async () => {
const { fetchAgent } = await import("../../api");
vi.mocked(fetchAgent).mockResolvedValue({ id: "agent-1", name: "Agent 1", role: "executor", state: "active" } as any);
render(
<TaskDetailModal
task={makeTask({ column: "triage", paused: true, assignedAgentId: "agent-1" })}
onClose={noop}
onMoveTask={noopMove}
onDeleteTask={noopDelete}
onMergeTask={noopMerge}
onOpenDetail={noopOpenDetail}
addToast={noop}
/>,
);
fireEvent.click(screen.getByRole("button", { name: /actions/i }));
expect(screen.queryByRole("menuitem", { name: "Pause" })).toBeNull();
expect(screen.queryByRole("menuitem", { name: "Unpause" })).toBeNull();
});
it("shows paused-by-agent indicator for agent-paused tasks", async () => {
const { fetchAgent } = await import("../../api");
vi.mocked(fetchAgent).mockResolvedValue({ id: "agent-1", name: "Agent 1", role: "executor", state: "paused" } as any);
render(
<TaskDetailModal
task={makeTask({ column: "triage", paused: true, assignedAgentId: "agent-1", pausedByAgentId: "agent-1" })}
onClose={noop}
onMoveTask={noopMove}
onDeleteTask={noopDelete}
onMergeTask={noopMerge}
onOpenDetail={noopOpenDetail}
addToast={noop}
/>,
);
fireEvent.click(screen.getByRole("button", { name: /actions/i }));
expect(screen.getByText("Paused by agent")).toBeTruthy();
});
it("does NOT render Actions dropdown for a non-paused, non-awaiting-approval, non-retryable triage task", () => {
render(
<TaskDetailModal