Follow-ups hang one step in under the first task of their line, so the list shows what goes on from what; a filter's tasks still show alone. Each row has the head's menu, without opening the task. Removing a task joins those after it to the one before. The pick list now stands over everything at the place the menu opened: the head is drawn anew under an open menu. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
72 lines
3.1 KiB
Python
72 lines
3.1 KiB
Python
"""Which task a task goes on from ("接着"), so work split over tasks is found together: a task an
|
|
AI begins as new while on another (ai_tasks.begin_task), or one the user starts while looking at
|
|
another (ai_tasks.start_task), keeps that one as its "from". The user can link a task to another
|
|
or take the link off (set_from); links() gives a task's own "from" and the tasks that go on from it,
|
|
and an AI beginning a task that goes on from another hears what was decided there. A task the user
|
|
removes (remove) leaves no gap: those that went on from it go on from what it went on from."""
|
|
|
|
from . import ai_decisions, ai_tasks
|
|
|
|
|
|
class LinkError(ValueError):
|
|
pass
|
|
|
|
|
|
def set_from(task_id, parent_id):
|
|
"""Task `task_id` goes on from `parent_id` (None: from none). Refuses a loop. Returns the parent id."""
|
|
with ai_tasks._lock:
|
|
tasks = ai_tasks._load()
|
|
task = tasks.get(task_id)
|
|
if not task:
|
|
raise LinkError("No such task")
|
|
if parent_id is not None:
|
|
if parent_id not in tasks or parent_id == task_id:
|
|
raise LinkError("No such other task")
|
|
seen, at = set(), parent_id
|
|
while at and at not in seen: # going up from the parent must not come back here
|
|
if at == task_id:
|
|
raise LinkError("That task already goes on from this one")
|
|
seen.add(at)
|
|
at = (tasks.get(at) or {}).get("from")
|
|
task["from"] = parent_id
|
|
else:
|
|
task.pop("from", None)
|
|
ai_tasks._save(task)
|
|
ai_tasks._notify(task_id)
|
|
return parent_id
|
|
|
|
|
|
def remove(task_id):
|
|
"""Takes task `task_id` off the list; those that went on from it now go on from what it went on from."""
|
|
with ai_tasks._lock:
|
|
tasks = ai_tasks._load()
|
|
parent = (tasks.get(task_id) or {}).get("from")
|
|
for task in tasks.values():
|
|
if task.get("from") == task_id:
|
|
if parent:
|
|
task["from"] = parent
|
|
else:
|
|
task.pop("from")
|
|
ai_tasks._save(task)
|
|
return ai_tasks.remove_task(task_id)
|
|
|
|
|
|
def links(task_id):
|
|
"""{from: {id, title} | None, next: [{id, title}]} of task `task_id`, the next oldest first."""
|
|
with ai_tasks._lock:
|
|
tasks = ai_tasks._load()
|
|
task = tasks.get(task_id) or {}
|
|
brief = lambda item: {"id": item["id"], "title": item.get("title") or ""} # noqa: E731
|
|
parent = tasks.get(task.get("from"))
|
|
later = sorted((item for item in tasks.values() if item.get("from") == task_id), key=lambda item: item.get("started", 0))
|
|
return {"from": brief(parent) if parent else None, "next": [brief(item) for item in later]}
|
|
|
|
|
|
def carried(task):
|
|
"""For an AI beginning `task`: the task it goes on from and what was decided there, or None."""
|
|
parent = ai_tasks.get_task(task.get("from")) if task.get("from") else None
|
|
if not parent:
|
|
return None
|
|
return {"task": parent["id"], "title": parent.get("title") or "", "decisions": ai_decisions.lines(parent),
|
|
"note": "This task goes on from that one; keep to what was decided there unless the user says otherwise."}
|