tasks carry a definition of done and a blocker (V-510)

Migration #22 adds done_when and blocked_on to tasks, both NOT NULL DEFAULT
''. "He has not written one" and "there is nothing to write" are the same
state here, so no caller has to tell NULL from empty.

blocked_on is a canonical Nexus entity id, never a name. It names a person
and identity lives in Nexus, so free text here would be a second answer to a
question Nexus already owns. The caller resolves before it writes.

Both columns round-trip through ipc.TaskAPI: on ipc.Task, settable at intake
through CaptureTaskReq, and writable afterwards through the new
SetTaskFields, which is deliberately not one-way — he may sharpen a
criterion, and a blocker clears when the person answers.

SetTaskStatus now refuses candidate → open when done_when is empty
(ErrTaskNoDoneWhen, mapped across the wire), the same refusal
ParseTaskCapture makes for a capture marker with nothing after it: confirming
work whose finish line nobody wrote is how a board fills with rows that can
never leave it. Dropping such a candidate stays legal, and the /tasks confirm
button now says what is missing instead of surfacing a not-found.

One caller skips the gate. CaptureTask promoting a candidate he stated out
loud would otherwise be denied intake rather than asked for a criterion, and
a direct open capture never carried one either. The gate belongs to the
deliberate promotion on /tasks, where V-511 puts a form.
This commit is contained in:
2026-08-05 20:17:30 +04:00
parent d21b4a65da
commit 496559c9dd
12 changed files with 228 additions and 7 deletions
+6
View File
@@ -1050,6 +1050,12 @@ func applyTaskPost(ctx context.Context, core ipc.CoreAPI, r *http.Request) (stri
return "", fmt.Errorf("unknown action %q", action)
}
if err := core.SetTaskStatus(ctx, id, status, now(), "tap:web"); err != nil {
if errors.Is(err, ipc.ErrTaskNoDoneWhen) {
// The refusal has to name what is missing, or the button looks
// broken. The field it asks for arrives with the intake form
// (Vikunja #511).
return "", errors.New("write a definition of done before confirming this candidate")
}
return "", err
}
return msg, nil