feat(KB-088): clear failed status when moving tasks and improve error display

- Fix moveTask to clear status/error/worktree/blockedBy when moving from in-progress to todo/triage
- Add error message display in TaskCard for failed tasks with truncation
- Add prominent error alert in TaskDetailModal for failed tasks
- Refactor usage tracking: move from app/components to dashboard/src/
- Add usage.ts and usage.test.ts for centralized usage tracking
- Remove UsageIndicator and useUsageData from app/ directory
- Update styles.css for error display components
- Update executor tests for error handling
- Add changeset for patch release
This commit is contained in:
gsxdsm
2026-03-29 21:48:26 -07:00
parent 63f530aac3
commit cf1bdda87f
16 changed files with 1186 additions and 1233 deletions

View File

@@ -165,105 +165,6 @@ describe("TaskDetailModal", () => {
expect(screen.queryByText("Retry")).toBeNull();
});
it("renders Retry button for failed tasks in any column (including done)", () => {
const columns: Column[] = ["triage", "todo", "in-progress", "in-review", "done"];
for (const column of columns) {
const { unmount } = render(
<TaskDetailModal
task={makeTask({ status: "failed", column })}
onClose={noop}
onMoveTask={noopMove}
onDeleteTask={noopDelete}
onMergeTask={noopMerge}
onOpenDetail={noopOpenDetail}
onRetryTask={noopRetry}
addToast={noop}
/>,
);
expect(screen.getByText("Retry")).toBeTruthy();
unmount();
}
});
it("renders error alert when task has failed status and error field", () => {
const { container } = render(
<TaskDetailModal
task={makeTask({ status: "failed", error: "Build failed: cannot find module" })}
onClose={noop}
onMoveTask={noopMove}
onDeleteTask={noopDelete}
onMergeTask={noopMerge}
onOpenDetail={noopOpenDetail}
addToast={noop}
/>,
);
const errorAlert = container.querySelector(".detail-error-alert");
expect(errorAlert).toBeTruthy();
expect(screen.getByText("Task Failed")).toBeTruthy();
expect(screen.getByText("Build failed: cannot find module")).toBeTruthy();
});
it("does NOT render error alert when task is not failed", () => {
const { container } = render(
<TaskDetailModal
task={makeTask({ status: "executing" })}
onClose={noop}
onMoveTask={noopMove}
onDeleteTask={noopDelete}
onMergeTask={noopMerge}
onOpenDetail={noopOpenDetail}
addToast={noop}
/>,
);
const errorAlert = container.querySelector(".detail-error-alert");
expect(errorAlert).toBeNull();
});
it("does NOT render error alert when task is failed but has no error message", () => {
const { container } = render(
<TaskDetailModal
task={makeTask({ status: "failed", error: undefined })}
onClose={noop}
onMoveTask={noopMove}
onDeleteTask={noopDelete}
onMergeTask={noopMerge}
onOpenDetail={noopOpenDetail}
addToast={noop}
/>,
);
const errorAlert = container.querySelector(".detail-error-alert");
expect(errorAlert).toBeNull();
});
it("calls onRetryTask when Retry button is clicked", async () => {
const mockRetry = vi.fn().mockResolvedValue({});
render(
<TaskDetailModal
task={makeTask({ status: "failed", column: "done" })}
onClose={noop}
onMoveTask={noopMove}
onDeleteTask={noopDelete}
onMergeTask={noopMerge}
onOpenDetail={noopOpenDetail}
onRetryTask={mockRetry}
addToast={noop}
/>,
);
const retryButton = screen.getByText("Retry");
fireEvent.click(retryButton);
await waitFor(() => {
expect(mockRetry).toHaveBeenCalledWith("KB-099");
});
});
it("shows description exactly once for a task without title", () => {
const { container } = render(
<TaskDetailModal