7.1 KiB
7.1 KiB
status, created, owner
| status | created | owner |
|---|---|---|
| building | 2026-08-04 | kandev |
Subtask re-parenting by drag and drop
Why
Users can detach a subtask or nest a task under another via context-menu actions, but moving a task from under one parent to under another takes two menu hops (un-nest, then nest under), and the sidebar's drag-and-drop only reorders siblings. A direct drag gesture should re-parent a task in one motion with the exact same result as those two menu actions.
What
- The sidebar task tree (desktop sidebar and the mobile task switcher sheet) lets a user re-parent a task by dragging its row onto another row's nest drop zone.
- The result is strictly equivalent to choosing
Un-nest (remove parent)thenNest under <target>from the task's context menu: the task's parent becomes the target, and a task whose workspace mode isinherit_parentends with modeshared_group— its materialized workspace and workspace-group membership are unchanged. - Drop targets are exactly the candidates the context menu offers (the
computeNestCandidatesrules): same-workflow root tasks in the visible group, excluding the dragged task, its current parent, and any subtask (which also excludes the task's own descendants). A task that already has children offers no nest targets. - While a drag with valid targets is active, candidate rows show a nest drop zone (a left-edge strip) with a
Nest under <title>affordance. Dropping on a zone re-parents; dropping between rows keeps the existing sibling-reorder behavior; any other drop is a no-op. - Re-parenting is a single API call on the existing canonical path (
PATCH /api/v1/tasks/:idwithparent_id), which already rejects self-parenting, missing/archived/cross-workspace targets, descendant cycles, and one-level-depth violations for kanban tasks. - The sidebar
Nest undermenu, the Office parent picker, the WS task-update path, and the Office dashboard PATCH all share the same composite semantics: any effective parent change normalizes aninherit_parentworkspace mode toshared_group. - No confirmation dialog is shown on drop (unlike the detach menu action): the gesture is explicit, the operation is non-destructive (workspace membership is retained) and reversible via the menu or another drag.
- Successful re-parenting is reflected across sidebar, board, and task-detail views without a reload, via the existing optimistic snapshot update and the
task.updatedWebSocket event. - The mobile task switcher sheet offers the same touch drag-and-drop re-parenting.
Data model
No table or column is added. Re-parenting updates existing persisted fields in the same task-row write:
tasks.parent_idbecomes the target task's id (the previous parent, if any, is replaced).tasks.metadata.workspace.modechanges frominherit_parenttoshared_groupwhen the parent relationship effectively changes and the mode wasinherit_parent. Other modes (shared_group,new_workspace) are unchanged.task_workspace_group_membersis unchanged; active membership remains the durable source of shared workspace access.- Descendant
parent_idvalues, blockers, sessions, repositories, workflow, workflow step, and state are unchanged.
API surface
No new endpoint. Reuses and extends existing contracts:
PATCH /api/v1/tasks/:idwithparent_id(non-empty nests;""un-nests) — already validated byService.resolveParentID(self, existence, archived, same workspace, descendant cycle) andvalidateReparentDepth(one-level kanban limit, Office trees exempt). Behavior addition: when the effective parent changes,inherit_parentworkspace mode is normalized toshared_group(mirroring the detach operation). Success returns the updated task DTO; invalid targets map to400; missing task to404.PATCH /api/v1/office/tasks/:idwith non-emptyparent_id— same normalization added for parity; empty parent continues to route through the canonical detach operation.- WS
task.updatedpayload unchanged:parent_idis always present (nil when cleared), andmetadatacarries the normalized workspace mode.
Failure modes
- Invalid target (self, descendant, subtask, archived, missing, cross-workspace, or depth violation): the UI filters these targets out before a drop can land, so a drop outside every nest zone is a no-op. If a request is nevertheless rejected by the backend (e.g. a valid-zone target became invalid between render and drop), the UI keeps the task in its original tree position, rolls back the optimistic update, and shows a request-error toast.
- Persistence failure: no successful response is returned; the UI rolls back to the original tree.
- Concurrent submissions are safe: setting the same parent twice is idempotent, and the optimistic update is reconciled by the authoritative
task.updatedevent. - No valid targets: the drag offers no nest zones; only reorder remains possible.
Scenarios
- GIVEN a subtask
Cunder parentAand a root taskBin the same workflow, WHEN the user dragsContoB's nest drop zone, THENC's parent becomesB, the sidebar showsCnested underB, and no reload occurs. - GIVEN an
inherit_parentsubtaskCunderA, WHENCis dragged onto rootB's nest zone, THENC's persisted workspace mode isshared_groupand its workspace-group membership is unchanged. - GIVEN a root task with no children, WHEN it is dragged onto another root's nest zone, THEN it becomes that root's subtask.
- GIVEN a task that already has children, WHEN it is dragged, THEN no row offers a nest drop zone (reorder only).
- GIVEN a drag over a subtask row or over a task in a different workflow, WHEN the pointer rests on the row, THEN no nest drop zone is offered.
- GIVEN a subtask dragged toward its current parent, WHEN the pointer rests on that parent row, THEN no nest drop zone is offered.
- GIVEN a drag dropped between two sibling rows, WHEN the drop lands outside every nest zone, THEN the siblings reorder as before.
- GIVEN a drop that lands outside every nest zone, WHEN the drop completes, THEN the task keeps its original parent and no request is sent (a plain no-op); a request-error toast appears only when a valid-zone drop's request is rejected by the backend.
- GIVEN a re-parented task, WHEN its
task.updatedevent arrives over WebSocket, THEN cached parent relationships in sidebar, board, and task-detail views are updated. - GIVEN the mobile task switcher sheet, WHEN a user touch-drags a subtask onto a root's nest zone, THEN the same re-parenting occurs.
Out of scope
- Nesting via drag on the kanban board (cards keep their step-move drag semantics).
- Lifting the one-level kanban subtask limit (still enforced by
computeNestCandidatesandvalidateReparentDepth; Office trees keep arbitrary depth). - Drag-to-re-parent in the Office task list (Office keeps its parent picker).
- Bulk or multi-select drag re-parenting.
- A keyboard/AT drag gesture; the existing context menus remain the accessible path to the same operation.