feat(tasks): allow subtasks up to three levels deep and re-parenting (backend)

オープン
#243 コメント 0 件 リアクション 0 件 担当者 0 名 GitHub で見る

メンテナーはふだん 1 日以内に返信

まだ誰も着手していません。

評価

難易度
5/5
見積もり時間
1週間以上
初心者へのやさしさ
15/100
issue の種類
機能追加
明瞭さ
説明が足りない
活発さ
活発
技術スタック
csharp
領域
backend

調査の方向性

The issue asks for either multi-level subtask nesting or re-parenting of a subtask, and does not choose between them. Before any code, a maintainer needs to decide the model and settle the effects on journal planning, completion rollup and the UI, which the payload lists but does not resolve. Done would mean an agreed design in the thread; the MCP side of get_journal is tracked separately in nevridge/taskflow-assistant#4.

索引モデルが issue の本文から書いたものです。

説明

enhancement

Groomed 2026-10-30, updated after splitting. Backend half. Part of #366.

Followed by #369 (the frontend half: context headers at three levels, eligible-parent selectors, history display, error messages), which is blocked by this issue. Runs after #368 (this story reuses its completed-parent rule), #396 (the same rule also rejects reopening a child under a Completed parent, checking the direct parent only; the ancestor walk is this story's, see criterion 13a) and #387 (which replaces nested journal rows with rows and context headers). Related: #354 (dead cycle error code cleanup; do it first). Also waits for #405 (an unblock path for a Blocked parent with open subtasks, which changes the same parent rules) and the monorepo move (#397), so this is the first API feature built with the merged pipeline and its MCP change ships in the same PR.

Original Description

Problem

Subtasks are limited to one level. A task under nevridge/taskflow-assistant#17 wasn't possible because nevridge/taskflow-assistant#17 was itself a subtask.

Proposal

Either allow more than one nesting level, or allow re-parenting a subtask. Consider the effect on journal planning (removing a task also removes its subtasks from that day), completion rollup, and the UI.

Context

Found while using Claude via the MCP on 2026-10-12/13. Split out of #241. MCP side (listing subtasks in get_journal) is tracked in nevridge/taskflow-assistant#4.


User Story

As a TaskFlow user, I want to nest subtasks up to three levels deep (task, subtask, sub-subtask) and move a task to a different parent or clear its parent, so that I can break work down as far as it really goes, and I'm not blocked when the task I want to add under is itself a subtask (as with nevridge/taskflow-assistant#17).

Decisions made during grooming:

  • Maximum depth is 3 levels (root, child, grandchild). It is defined in one named constant so it can be raised later.
  • Auto-complete-parent cascades up every level.
  • Moving a task to a different parent (including clearing it) records a new ParentChanged history event.
  • A task that already has children may become a child (its whole subtree moves with it), as long as the resulting subtree stays within depth 3.
  • Removing a task from a journal day also removes all its linked descendants from that day.
  • ChildTaskCount stays direct children only.
  • No database migration: the schema already allows any depth.
  • Re-parenting through clear-parent, PUT and PATCH keeps working.
  • No separate cycle check or error code. At a maximum depth of 3 a cycle can never reach a cycle check: a task with a descendant has a subtree at least 2 levels tall, so moving it under one of its own descendants always pushes the subtree to depth 4 or more, and the depth rule rejects it first. A self-parent is rejected by the self rule. This is the same reason the old cycle check was unreachable and was deleted in #353.

Acceptance Criteria

  1. Grandchild create. Given a root task A with child B, when I create task C with B as parent, then C is saved as a grandchild (depth 3) and B's ChildTaskCount is 1.
  2. Depth limit. Given a grandchild C (depth 3), when I create or move a task under C, then the request is rejected with TASK_PARENT_DEPTH_NOT_ALLOWED and nothing changes.
  3. Subtree move. Given a task X with its own children, when I set its parent to a task at depth 2, then the request is rejected because the subtree would reach depth 4. When the parent is at depth 1 or is a root, it succeeds and all of X's descendants move with it.
  4. Self and descendant parent. Given a task, when I set its parent to itself (PUT or PATCH), then it is rejected with TASK_PARENT_SELF_NOT_ALLOWED. When I set its parent to any of its own descendants, then it is rejected with TASK_PARENT_DEPTH_NOT_ALLOWED. Nothing changes in either case. No new cycle error code is added. The maximum-depth constant carries a comment saying that raising it to 4 or more requires a cycle check and its own error code, because a cycle then becomes reachable.
  5. Re-parent history. Given a task, when I change its parent to another valid task or clear it (PUT or PATCH), then the change is saved and one ParentChanged history event is recorded with a change summary.
  6. No spurious events. Given a task, when an update leaves the parent unchanged, then no ParentChanged event is recorded.
  7. Cascade up. Given a grandchild that is the last open child of a child, and that child is the last open child of a parent, and AutoCompleteParentWhenChildrenDone applies, when I complete the grandchild, then the child is completed and then the parent is completed.
  8. Cascade stops. Given a middle-level task that still has another open child, when the last open grandchild under one of its children is completed, then the cascade stops at the level that still has open children and does not complete anything above it.
  9. Complete guard. Given a task with any open descendant (child or grandchild), when I mark it complete, then the request is rejected with the existing open-children error (TASK_PARENT_COMPLETE_BLOCKED_BY_CHILDREN).
  10. Delete guard. Given a task with any descendant, when I delete it, then the request is rejected with the existing has-children error (TASK_PARENT_DELETE_BLOCKED_BY_CHILDREN).
  11. Journal removal. Given a task and its descendants linked to a journal day, when I remove the task from that day, then the task and all its linked descendants (children and grandchildren) are removed from that day. Descendants not linked to that day are unaffected.
  12. Child count. Given a task response, when it has children and grandchildren, then ChildTaskCount equals the number of direct children only.
  13. Completed parent. Given a Completed parent, when I create an open child under it or move an open task under it, then the request is rejected by the same check and error code that #368 adds. This story does not add a second check. #368 skipped a priority test against TASK_PARENT_STATUS_DERIVED because, at one level of depth, a task with open children hits the depth error first. At depth 3 that path becomes reachable (a request that moves a parent with open children under a Completed parent and also changes its status), so add the test here: decide which of TASK_PARENT_STATUS_DERIVED and TASK_PARENT_COMPLETED_NOT_ALLOWED wins, and pin it for both PUT and PATCH.
    13a. Completed ancestors. #368 (attach or create) and #396 (reopen) check the direct parent only, which is enough at one level. At three levels, extend the same check, with the same code TASK_PARENT_COMPLETED_NOT_ALLOWED and message, to any Completed ancestor, for attach, create, move and reopen. Given a Completed grandparent with an open parent, an open grandchild created or a Completed grandchild reopened under it is rejected; given an open chain up to the root, it is accepted. This story adds no second error code.
  14. Documentation and client. The depth error message names the 3-level limit (it no longer says "one level"). docs/API.md (the TASK_PARENT_DEPTH_NOT_ALLOWED entry) and docs/FRONTEND.md error list are updated. The API client is regenerated by the backend-builder only; sdk.gen.ts and types.gen.ts are never hand-edited.

Edge Cases

  • A subtask whose parent isn't on its day is a row with a context header (#387). With three levels, the response must include the full path to the top-level goal.
  • Recheck criterion 11 against #387's decision on planning goals and on existing parents already linked to a day.
  • Carry-forward moves tasks independently, so a tree can split across days. Carry-forward semantics are unchanged, but check that the deeper tree still displays sensibly.
  • Concurrent requests (for example two moves at once) could together exceed depth 3. Likelihood is low. Decide whether the check and the write need to be atomic.
  • The Include chains in JournalEntryRepository (four call sites) must load three levels, and the mapping must follow the row shape from #387. A missing Include silently drops grandchildren.
  • MCP clients will now receive deeper trees. This story waits for the move of taskflow-assistant into this repo (#397), so the MCP change ships in the same PR as this API change and runs the integration test (nevridge/TaskFlow#411) and the error-code mapping test (#399). When this story is built, add an assistant step (assistant-builder): get_journal and the tool descriptions show deeper trees, and the matching skills, agent, chat references and README change with it.
  • Rebase after #356, #361, #352, #350 and #387, which also edit TaskRepository.UpdateAsync, ApplyUpdateAsync and MapTodo. Line numbers in older notes are stale; find tests and code by name.

Out of Scope

  • The frontend work (#369).
  • MCP changes beyond showing deeper trees in get_journal (listing subtasks was nevridge/taskflow-assistant#4, a separate repo until the move in #397).
  • A cycle check or cycle error code (unreachable at depth 3).
  • Drag-and-drop re-parenting.
  • Bulk subtree move or copy.
  • Depth beyond 3 (the constant makes this a later change).
  • Database migration.
  • Changes to carry-forward semantics.
  • Unrelated #241 work.

Implementation Notes

Guidance for builders, not acceptance criteria.

  • Update docs/API.md and docs/FRONTEND.md.
  • Tests to rewrite or extend, by name: TaskItemsControllerV1Tests and TaskItemsControllerPatchTests (depth and parent tests), JournalTodosControllerV1Tests (orphan and child tests), TaskRepositoryTests (parent tests), JournalEntryRepositoryTests (child include and remove tests).
  • Backend touchpoints: TaskItemsController (ApplyUpdateAsync, TryAutoCompleteParentAsync needs upward recursion, Create parent checks, Delete), TaskRepository.UpdateAsync (add the ParentChanged event), JournalTodosController.RemoveTodo and JournalEntryRepository.RemoveTodoAsync (full descendant cascade). The children-become-child case currently reuses the DEPTH error code and message.
主要言語
C#
スター
1
フォーク
0
平均マージ
2時間 2分
マージ済み PR(30日)
98

環境構築

はじめの一歩

  1. issue を最後まで読み、次にプロジェクトのコントリビューションガイドを読みます。
  2. 着手することを issue にコメントします — 二人が同じ作業をするのを防げます。
  3. リポジトリをフォークし、ブランチを切って変更します。
  4. issue 番号を参照したプルリクエストを送ります。

nevridge/TaskFlow のほかの issue

nevridge/TaskFlow の issue をすべて見る

似ている issue

C# の issue をもっと見る

新しい issue をメールで受け取る

初心者向けの GitHub issue を短くまとめたダイジェスト。