feat(HAI-057): add task dependency editing to dashboard UI

- Add dependencies field support to store updateTask method
- Wire dependencies through PATCH route and client API
- Add dependency editing UI (add/remove) to TaskDetailModal
- Pass tasks prop from App to TaskDetailModal for dependency picker
- Add tests for dependency CRUD in modal and API layer
This commit is contained in:
Dustin Byrne
2026-03-26 00:15:29 -04:00
parent e41d3936d6
commit efda541713
9 changed files with 318 additions and 11 deletions

View File

@@ -348,4 +348,41 @@ describe("TaskStore", () => {
expect(result.log).toHaveLength(initialLogCount + 10);
});
});
describe("updateTask — dependencies", () => {
it("adds dependencies to a task with none", async () => {
const task = await createTestTask();
expect(task.dependencies).toEqual([]);
const updated = await store.updateTask(task.id, { dependencies: ["HAI-001", "HAI-002"] });
expect(updated.dependencies).toEqual(["HAI-001", "HAI-002"]);
// Verify persistence
const fetched = await store.getTask(task.id);
expect(fetched.dependencies).toEqual(["HAI-001", "HAI-002"]);
});
it("replaces existing dependencies", async () => {
const task = await store.createTask({ description: "Dep task", dependencies: ["HAI-001"] });
expect(task.dependencies).toEqual(["HAI-001"]);
const updated = await store.updateTask(task.id, { dependencies: ["HAI-002", "HAI-003"] });
expect(updated.dependencies).toEqual(["HAI-002", "HAI-003"]);
});
it("clears dependencies with empty array", async () => {
const task = await store.createTask({ description: "Dep task", dependencies: ["HAI-001"] });
expect(task.dependencies).toEqual(["HAI-001"]);
const updated = await store.updateTask(task.id, { dependencies: [] });
expect(updated.dependencies).toEqual([]);
});
it("leaves dependencies unchanged when not provided", async () => {
const task = await store.createTask({ description: "Dep task", dependencies: ["HAI-001"] });
const updated = await store.updateTask(task.id, { title: "New title" });
expect(updated.dependencies).toEqual(["HAI-001"]);
});
});
});

View File

@@ -282,7 +282,7 @@ export class TaskStore extends EventEmitter<TaskStoreEvents> {
async updateTask(
id: string,
updates: { title?: string; description?: string; prompt?: string; worktree?: string; status?: string | null; blockedBy?: string | null },
updates: { title?: string; description?: string; prompt?: string; worktree?: string; status?: string | null; dependencies?: string[] },
): Promise<Task> {
return this.withTaskLock(id, async () => {
const dir = this.taskDir(id);
@@ -291,6 +291,7 @@ export class TaskStore extends EventEmitter<TaskStoreEvents> {
if (updates.title !== undefined) task.title = updates.title;
if (updates.description !== undefined) task.description = updates.description;
if (updates.worktree !== undefined) task.worktree = updates.worktree;
if (updates.dependencies !== undefined) task.dependencies = updates.dependencies;
if (updates.status === null) {
task.status = undefined;
} else if (updates.status !== undefined) {