8.6 KiB
8.6 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.
- A drag starts only from a row's body. Interacting with a row's context menu (including the Color submenu and every item inside it) never starts a drag and never activates the row: pointer-start and click events inside the menu are contained within the menu and do not reach the row's drag sensor listeners or its click handler.
- On touch, the long-press that opens the context menu cancels any touch-drag the hold started: the menu opens without the row moving, no nest drop zones remain while the menu is open, and continuing to drag inside the open menu does not move or reorder the row. (The drag sensor arms after a 250ms hold, before the ≈700ms long-press opens the menu, so the menu cancels the in-flight drag instead of dropping it.)
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.
- GIVEN a task row whose context menu is open, WHEN the user presses and moves inside the menu (for example on the Color submenu trigger or one of its swatches), THEN no drag starts: no nest drop zones appear, the row neither dims nor moves, and the menu item still works.
- GIVEN a task row whose context menu is open, WHEN the user clicks a menu item, THEN the row is not activated or selected as a side effect of the click.
- GIVEN a touch long-press on a task row, WHEN the context menu opens, THEN the touch-drag started by the hold is cancelled: no nest drop zones remain and the row is not dimmed while the menu is open, and dragging inside the open menu does not move or reorder the row.
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.