feat(KB-040): add break-into-subtasks toggle for task creation
- Add breakIntoSubtasks flag to core Task model and persist to store - Plumb subtask flag through dashboard API routes with validation - Add subtask toggle UI to inline task creation card - Update triage agent to handle automatic subtask breakdown when enabled - Add comprehensive tests for store, API routes, and UI components - Include changeset for patch release documenting the new feature
This commit is contained in:
@@ -150,6 +150,78 @@ describe("GET /tasks/:id", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("POST /tasks", () => {
|
||||
let store: TaskStore;
|
||||
|
||||
beforeEach(() => {
|
||||
store = createMockStore();
|
||||
});
|
||||
|
||||
function buildApp() {
|
||||
const app = express();
|
||||
app.use(express.json());
|
||||
app.use("/api", createApiRoutes(store));
|
||||
return app;
|
||||
}
|
||||
|
||||
it("creates a task and forwards breakIntoSubtasks", async () => {
|
||||
const createdTask = {
|
||||
...FAKE_TASK_DETAIL,
|
||||
column: "triage",
|
||||
breakIntoSubtasks: true,
|
||||
};
|
||||
(store.createTask as ReturnType<typeof vi.fn>).mockResolvedValue(createdTask);
|
||||
|
||||
const res = await REQUEST(
|
||||
buildApp(),
|
||||
"POST",
|
||||
"/api/tasks",
|
||||
JSON.stringify({
|
||||
description: "Big initiative",
|
||||
breakIntoSubtasks: true,
|
||||
}),
|
||||
{ "Content-Type": "application/json" },
|
||||
);
|
||||
|
||||
expect(res.status).toBe(201);
|
||||
expect(store.createTask).toHaveBeenCalledWith({
|
||||
title: undefined,
|
||||
description: "Big initiative",
|
||||
column: undefined,
|
||||
dependencies: undefined,
|
||||
breakIntoSubtasks: true,
|
||||
});
|
||||
});
|
||||
|
||||
it("returns 400 when description is missing", async () => {
|
||||
const res = await REQUEST(
|
||||
buildApp(),
|
||||
"POST",
|
||||
"/api/tasks",
|
||||
JSON.stringify({ breakIntoSubtasks: true }),
|
||||
{ "Content-Type": "application/json" },
|
||||
);
|
||||
|
||||
expect(res.status).toBe(400);
|
||||
expect(res.body.error).toContain("description is required");
|
||||
expect(store.createTask).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("returns 400 when breakIntoSubtasks is not a boolean", async () => {
|
||||
const res = await REQUEST(
|
||||
buildApp(),
|
||||
"POST",
|
||||
"/api/tasks",
|
||||
JSON.stringify({ description: "Big initiative", breakIntoSubtasks: "yes" }),
|
||||
{ "Content-Type": "application/json" },
|
||||
);
|
||||
|
||||
expect(res.status).toBe(400);
|
||||
expect(res.body.error).toContain("breakIntoSubtasks must be a boolean");
|
||||
expect(store.createTask).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe("POST /tasks/:id/retry", () => {
|
||||
let store: TaskStore;
|
||||
|
||||
|
||||
@@ -610,16 +610,21 @@ export function createApiRoutes(store: TaskStore, options?: ServerOptions): Rout
|
||||
// Create task
|
||||
router.post("/tasks", async (req, res) => {
|
||||
try {
|
||||
const { title, description, column, dependencies } = req.body;
|
||||
const { title, description, column, dependencies, breakIntoSubtasks } = req.body;
|
||||
if (!description || typeof description !== "string") {
|
||||
res.status(400).json({ error: "description is required" });
|
||||
return;
|
||||
}
|
||||
if (breakIntoSubtasks !== undefined && typeof breakIntoSubtasks !== "boolean") {
|
||||
res.status(400).json({ error: "breakIntoSubtasks must be a boolean" });
|
||||
return;
|
||||
}
|
||||
const task = await store.createTask({
|
||||
title,
|
||||
description,
|
||||
column,
|
||||
dependencies,
|
||||
breakIntoSubtasks,
|
||||
});
|
||||
res.status(201).json(task);
|
||||
} catch (err: any) {
|
||||
|
||||
Reference in New Issue
Block a user