fix(panel): align task actions to server contract + collect required audit notes

The panel's human action buttons had drifted from the server request
schemas: wrong field names (qa_notes/reason vs notes), missing bodies
(cancel/complete/submit-pm-review), and a bare-string docs-complete body —
so cancel/pass-qa/fail-qa/escalate-to-ceo 4xx'd and decisions recorded no
audit note. (Agents were unaffected — they go through the gateway.)

- tasks.ts: pass-qa/fail-qa -> {notes}; escalate-to-ceo -> {notes:reason};
  cancel -> {reason}; complete -> {justification}; docs-complete -> {notes};
  submit-pm-review -> {notes}.
- New reusable RequiredNotesDialog (generalizes CeoApproveDialog). Every
  decision action now collects a substantive note before POSTing: cancel
  (>=10), pass-qa/fail-qa/docs-complete/submit-pm-review/complete (>=20),
  matching the server gates. Wired in the task detail page, the actions
  dropdown, and the kanban board.

Verified: pnpm tsc --noEmit and eslint both clean.
This commit is contained in:
Renn F
2026-05-24 07:10:34 +02:00
parent 5120b5ce81
commit bc5e016d6d
6 changed files with 429 additions and 51 deletions
+11 -11
View File
@@ -242,7 +242,7 @@ export const tasksApi = {
mockTasks[idx] = { ...mockTasks[idx], status: TaskStatus.AWAITING_DOCUMENTATION, qa_verified: true, qa_notes: qaNotes ?? null, updated_at: now };
return mockTasks[idx];
}
const { data } = await api.post<Task>("/tasks/" + taskId + "/pass-qa", { qa_notes: qaNotes });
const { data } = await api.post<Task>("/tasks/" + taskId + "/pass-qa", { notes: qaNotes });
return data;
},
@@ -254,11 +254,11 @@ export const tasksApi = {
mockTasks[idx] = { ...mockTasks[idx], status: TaskStatus.NEEDS_REVISION, qa_verified: false, qa_notes: qaNotes ?? null, updated_at: now };
return mockTasks[idx];
}
const { data } = await api.post<Task>("/tasks/" + taskId + "/fail-qa", { qa_notes: qaNotes });
const { data } = await api.post<Task>("/tasks/" + taskId + "/fail-qa", { notes: qaNotes });
return data;
},
complete: async (taskId: string): Promise<Task> => {
complete: async (taskId: string, justification: string): Promise<Task> => {
if (isMockMode()) {
const idx = mockTasks.findIndex((t) => t.id === taskId);
if (idx === -1) throw new Error("Task not found");
@@ -266,11 +266,11 @@ export const tasksApi = {
mockTasks[idx] = { ...mockTasks[idx], status: TaskStatus.COMPLETED, completed_at: now, updated_at: now };
return mockTasks[idx];
}
const { data } = await api.post<Task>("/tasks/" + taskId + "/complete");
const { data } = await api.post<Task>("/tasks/" + taskId + "/complete", { justification });
return data;
},
cancel: async (taskId: string): Promise<Task> => {
cancel: async (taskId: string, reason: string): Promise<Task> => {
if (isMockMode()) {
const idx = mockTasks.findIndex((t) => t.id === taskId);
if (idx === -1) throw new Error("Task not found");
@@ -278,7 +278,7 @@ export const tasksApi = {
mockTasks[idx] = { ...mockTasks[idx], status: TaskStatus.CANCELLED, updated_at: now };
return mockTasks[idx];
}
const { data } = await api.post<Task>("/tasks/" + taskId + "/cancel");
const { data } = await api.post<Task>("/tasks/" + taskId + "/cancel", { reason });
return data;
},
@@ -310,7 +310,7 @@ export const tasksApi = {
},
// Mark documentation as complete (Documenter only)
docsComplete: async (taskId: string, docNotes?: string): Promise<Task> => {
docsComplete: async (taskId: string, docNotes: string): Promise<Task> => {
if (isMockMode()) {
const idx = mockTasks.findIndex((t) => t.id === taskId);
if (idx === -1) throw new Error("Task not found");
@@ -318,7 +318,7 @@ export const tasksApi = {
mockTasks[idx] = { ...mockTasks[idx], status: TaskStatus.AWAITING_PM_REVIEW, updated_at: now };
return mockTasks[idx];
}
const { data } = await api.post<Task>("/tasks/" + taskId + "/docs-complete", docNotes ?? null);
const { data } = await api.post<Task>("/tasks/" + taskId + "/docs-complete", { notes: docNotes });
return data;
},
@@ -517,7 +517,7 @@ export const tasksApi = {
return data;
},
submitPmReview: async (taskId: string): Promise<Task> => {
submitPmReview: async (taskId: string, notes: string): Promise<Task> => {
if (isMockMode()) {
const idx = mockTasks.findIndex((t) => t.id === taskId);
if (idx === -1) throw new Error("Task not found");
@@ -529,7 +529,7 @@ export const tasksApi = {
};
return mockTasks[idx];
}
const { data } = await api.post<Task>("/tasks/" + taskId + "/submit-pm-review");
const { data } = await api.post<Task>("/tasks/" + taskId + "/submit-pm-review", { notes });
return data;
},
@@ -626,7 +626,7 @@ export const tasksApi = {
message: "Task escalated to CEO (mock)",
};
}
const { data } = await api.post<EscalateResponse>("/tasks/" + taskId + "/escalate-to-ceo", { reason });
const { data } = await api.post<EscalateResponse>("/tasks/" + taskId + "/escalate-to-ceo", { notes: reason });
return data;
},