fix(api): do the things the API says yes to
Release / Build and publish image (push) Failing after 9m58s

Three reports from somebody driving the app through the MCP connector,
all the same shape: an argument accepted, an OK returned, and nothing
done.

**project_id on an update never moved the task.** Both the REST handler
and the MCP tool read it, both checked the caller's rights on the
destination — which reads as "understood" — and then neither passed it
on, because store.TaskUpdate has no such field: a project change is a
move, since sort_order is per project. So the task stayed where it was
and the answer said OK. Moving a task between projects was impossible
except by creating it again and closing the old one, which is what the
person ended up doing. Both paths now go through one helper, so they
cannot drift apart again.

**An unknown #project vanished without a word.** The parser reads
"#3Dekoration" correctly; the lookup then fails, the task goes to the
Inbox, and the name is gone from the title with nothing anywhere saying
why. Still not an error — a thought should not be thrown away over a typo
— but quick-add now returns unknown_project, and the MCP tool returns a
note written for a model to relay. The old behaviour was kind in the web
interface, where you see the row appear in the wrong place and fix it. In
a chat nobody sees the row.

**Sub-tasks were unreachable over MCP.** create_task had no parent_id,
though the REST API has had it all along, so importing a parent with
seven children produced eight unrelated tasks with the parent's name
prefixed onto each. A sub-task joins its parent's project — the two
cannot disagree — and a second level is refused rather than silently
flattened.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Kristian Wind
2026-08-17 16:24:58 +02:00
committed by Kristian Wind
co-authored by Claude Opus 5
parent 8c748c2a88
commit afc46a5c20
4 changed files with 310 additions and 6 deletions
+24 -2
View File
@@ -533,7 +533,11 @@ paths:
type: string
description: Where it goes when the text names no project.
responses:
"201": { $ref: "#/components/responses/Task" }
"201":
description: Created
content:
application/json:
schema: { $ref: "#/components/schemas/QuickAddResult" }
"422": { $ref: "#/components/responses/Validation" }
/api/v1/tasks/quick-add/preview:
@@ -2135,7 +2139,12 @@ components:
TaskInput:
type: object
properties:
project_id: { type: string, description: "Omitted on create, the task goes to the Inbox." }
project_id:
type: string
description: |
Omitted on create, the task goes to the Inbox. On update it moves the
task — which needs editor rights on the destination.
section_id: { type: string }
parent_id: { type: string }
content: { type: string }
@@ -2151,6 +2160,19 @@ components:
items: { type: string }
sort_order: { type: number }
QuickAddResult:
allOf:
- $ref: "#/components/schemas/Task"
- type: object
properties:
unknown_project:
type: string
description: |
Present when the text named a `#project` that does not exist. The
task is still created, in the Inbox — a thought is not thrown away
over a typo — but the caller is told, because the alternative is
that the name vanishes from the title and nothing says why.
ParsedTask:
type: object
description: What the quick-add parser made of a line.