From 9191ab5b27e25caf0bbfaaa60d64727a6c25ac9d Mon Sep 17 00:00:00 2001 From: Bryan Van Deusen Date: Wed, 13 May 2026 12:16:43 -0400 Subject: [PATCH] feat(tasks): POST /api/tasks/:id/consolidate + separate body/description fields New endpoint manually triggers a consolidation pass for a single task. Bypasses the auto_consolidate_tasks setting since the user is asking explicitly. Returns the task with the freshly-written body and consolidated_at timestamp. Also un-aliases description and body in the create/update task routes (was: description folded into body as legacy fallback). With separate fields under the task-as-durable-record design, both flow through as distinct kwargs to create_note / update_note. --- src/fabledassistant/routes/tasks.py | 36 +++++++++++++++++++++++++---- 1 file changed, 32 insertions(+), 4 deletions(-) diff --git a/src/fabledassistant/routes/tasks.py b/src/fabledassistant/routes/tasks.py index ae3c974..c716914 100644 --- a/src/fabledassistant/routes/tasks.py +++ b/src/fabledassistant/routes/tasks.py @@ -89,7 +89,11 @@ async def list_tasks_route(): async def create_task_route(): uid = get_current_user_id() data = await request.get_json() - body = data.get("body", "") or data.get("description", "") + # Description (user-stated goal) and body (machine-maintained summary) are + # separate fields under the task-as-durable-record design. Don't fold one + # into the other. + body = data.get("body", "") + description = data.get("description") tags = data.get("tags", []) due_date = parse_iso_date(data.get("due_date"), "due_date") @@ -120,6 +124,7 @@ async def create_task_route(): uid, title=data.get("title", ""), body=body, + description=description, status=status, priority=priority, due_date=due_date, @@ -178,11 +183,12 @@ async def update_task_route(task_id: int): except ValueError: return jsonify({"error": f"Invalid priority: {data['priority']}"}), 400 - # Accept both "body" and "description" (prefer body) + # Body and description are distinct fields under the task-as-durable-record + # design. Don't alias one to the other. if "body" in data: fields["body"] = data["body"] - elif "description" in data: - fields["body"] = data["description"] + if "description" in data: + fields["description"] = data["description"] if "due_date" in data: if data["due_date"]: @@ -276,3 +282,25 @@ async def delete_task_route(task_id: int): if not deleted: return not_found("Task") return "", 204 + + +@tasks_bp.route("//consolidate", methods=["POST"]) +@login_required +async def consolidate_task_route(task_id: int): + """Manually trigger a consolidation pass for a task. + + Bypasses the auto_consolidate_tasks setting (the user is asking + explicitly). Returns the task's updated state including the freshly- + written body and consolidated_at timestamp. + """ + uid = get_current_user_id() + if not await can_write_note(uid, task_id): + return jsonify({"error": "Permission denied"}), 403 + + from fabledassistant.services.consolidation import consolidate_task + await consolidate_task(uid, task_id) + + note = await get_note(uid, task_id) + if note is None: + return not_found("Task") + return jsonify(note.to_dict())