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
+10 -6
View File
@@ -158,24 +158,26 @@ export function useTaskLifecycle() {
});
const passQa = useMutation({
mutationFn: ({ taskId, qaNotes }: { taskId: string; qaNotes?: string }) =>
mutationFn: ({ taskId, qaNotes }: { taskId: string; qaNotes: string }) =>
tasksApi.passQa(taskId, qaNotes),
onSuccess: invalidateTask,
});
const failQa = useMutation({
mutationFn: ({ taskId, qaNotes }: { taskId: string; qaNotes?: string }) =>
mutationFn: ({ taskId, qaNotes }: { taskId: string; qaNotes: string }) =>
tasksApi.failQa(taskId, qaNotes),
onSuccess: invalidateTask,
});
const complete = useMutation({
mutationFn: (taskId: string) => tasksApi.complete(taskId),
mutationFn: ({ taskId, justification }: { taskId: string; justification: string }) =>
tasksApi.complete(taskId, justification),
onSuccess: invalidateTask,
});
const cancel = useMutation({
mutationFn: (taskId: string) => tasksApi.cancel(taskId),
mutationFn: ({ taskId, reason }: { taskId: string; reason: string }) =>
tasksApi.cancel(taskId, reason),
onSuccess: invalidateTask,
});
@@ -190,12 +192,14 @@ export function useTaskLifecycle() {
});
const docsComplete = useMutation({
mutationFn: (taskId: string) => tasksApi.docsComplete(taskId),
mutationFn: ({ taskId, notes }: { taskId: string; notes: string }) =>
tasksApi.docsComplete(taskId, notes),
onSuccess: invalidateTask,
});
const submitPmReview = useMutation({
mutationFn: (taskId: string) => tasksApi.submitPmReview(taskId),
mutationFn: ({ taskId, notes }: { taskId: string; notes: string }) =>
tasksApi.submitPmReview(taskId, notes),
onSuccess: invalidateTask,
});