feat(HAI-055): add failed task status handling with retry support

- Set task status to 'failed' on execution failure in engine executor
- Add failed indicator styling on TaskCard component
- Add POST /tasks/:id/retry API endpoint and client function
- Add retry button in TaskDetailModal for failed tasks
- Add tests for failed indicator, retry endpoint, and modal behavior
This commit is contained in:
Dustin Byrne
2026-03-25 23:50:11 -04:00
parent 95ec4bc4a1
commit 35177f448f
13 changed files with 243 additions and 12 deletions

View File

@@ -22,6 +22,7 @@ const noop = vi.fn();
const noopMove = vi.fn(async () => ({}) as Task);
const noopDelete = vi.fn(async () => ({}) as Task);
const noopMerge = vi.fn(async () => ({ merged: false }) as MergeResult);
const noopRetry = vi.fn(async () => ({}) as Task);
describe("TaskDetailModal", () => {
it("renders markdown-body without detail-prompt class when prompt exists", () => {
@@ -91,6 +92,53 @@ describe("TaskDetailModal", () => {
expect(screen.queryByText("PROMPT.md")).toBeNull();
});
it("renders Retry button when task status is 'failed'", () => {
render(
<TaskDetailModal
task={makeTask({ status: "failed" })}
onClose={noop}
onMoveTask={noopMove}
onDeleteTask={noopDelete}
onMergeTask={noopMerge}
onRetryTask={noopRetry}
addToast={noop}
/>,
);
expect(screen.getByText("Retry")).toBeTruthy();
});
it("does NOT render Retry button when task status is not 'failed'", () => {
render(
<TaskDetailModal
task={makeTask({ status: "executing" })}
onClose={noop}
onMoveTask={noopMove}
onDeleteTask={noopDelete}
onMergeTask={noopMerge}
onRetryTask={noopRetry}
addToast={noop}
/>,
);
expect(screen.queryByText("Retry")).toBeNull();
});
it("does NOT render Retry button when onRetryTask is not provided", () => {
render(
<TaskDetailModal
task={makeTask({ status: "failed" })}
onClose={noop}
onMoveTask={noopMove}
onDeleteTask={noopDelete}
onMergeTask={noopMerge}
addToast={noop}
/>,
);
expect(screen.queryByText("Retry")).toBeNull();
});
it("shows description exactly once for a task without title", () => {
const { container } = render(
<TaskDetailModal