feat(outline): PROG-118 drag actions between arcs and focuses#96
Merged
Conversation
The Outline's cross-focus drag (next commit) needs an action to land where it was released, not always loose at its old rank. POST /api/actions/:id/move now accepts optional arcId (validated to belong to the TARGET focus) and rank (fractional-key validated); the client moveAction mirrors both in its optimistic write and still restores the full prior row on failure. Both fields are optional, so the palette's focus-only move is unchanged.
PROG-86 made a drop outside the row's own sibling group a deliberate no-op, deferring drag-to-reparent. This lands it: ONE page-wide DndContext replaces the nested per-focus contexts (which made cross-section drags structurally impossible), and onDragEnd branches on what was picked up. Sections keep their PROG-87 reorder-only semantics; an action row dropped outside its group now moves — onto a row in another group it joins that group where released (one optimistic PATCH arcId/parentActionId/rank), onto an arc section it appends to that arc's top level, and into another focus it is a real move (re-key + alias, steps detach) landing top-level at the drop spot. New pure helpers, both unit-tested: rankForInsert (slot into a foreign group above/below the hovered row by the board's pointer-past-middle rule) and inSubtreeOf (refuse a drop into the action's own subtree before the doomed write). Hide-done filtering moved up to the page so drop targets resolve against the same rendered groups the forests draw.
… handles outline-move.spec.ts proves the two new drops with real pointer events: an action dragged from one arc into another (server-side arcId changes), and one dragged into another focus at workspace scope (focusId changes, re-keyed from the target's sequence, old key retired to an alias, survives reload). outline-container-reorder.spec.ts had been red since PROG-111: its selectors still targeted the pre-consolidation 'Reorder <name>' grip buttons. The handle is now the bullet anchor (exposed as role=button by dnd-kit's sortable attributes) named 'Open <name> — drag to reorder' — both specs pass again.
REFERENCE: the Outline surface now describes the page-wide DndContext and the drop rules (join the hovered group / append to an arc / real move cross-focus), and the move endpoint row + containment rules gain the optional arcId/rank landing spot. Decision log: docs/decisions/PROG-118.md.
…ttle on drop
Owner feedback: the first cut moved rows correctly but read as inert — no
grabbed-it feedback, no landing indication in foreign groups, and the release
popped. Bring the board's drag feel to the outline:
- A held row is carried by a floating DragOverlay card (bullet + title +
capped step subtree, board-card rotation/shadow, max-w-md) that tracks the
pointer across arc and focus sections; the in-list source dims to a ghost
but keeps its sorting translate (exactly BoardCard — an untransformed ghost
fights the strategy's neighbour displacement and overlaps rows).
- onDragOver live preview (the board's PROG-59 pattern): hovering a different
sibling group re-homes the row there in the rendered list, so the target
group — in any arc or focus — visibly opens the landing slot. The drop
commits the preview rather than re-resolving over (which is usually the
active row itself after a preview); over only fine-tunes the position
within the previewed group. MeasuringStrategy.Always keeps droppable rects
fresh while the preview moves real layout mid-drag.
- Real drop animation replaces dropAnimation={null}: the ~180ms default tween
glides the overlay into the committed slot. Safe now that PROG-119 notifies
cache writes synchronously — the destination row is already re-rendered
when the tween measures it, so no fly-back; verified frame-by-frame (row
stationary at its final position from frame 0 after mouseup).
The e2e drag helper now asserts the overlay is present mid-drag and clears
after the drop settles.
REFERENCE: the Outline surface now describes the floating row card, the live cross-group landing preview, and the settle-on-drop tween (and why PROG-119 makes it safe). Decision log: PROG-118b appended to docs/decisions/PROG-118.md.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes the PROG-118 ask: on the Outline, dragging an action can now move it to a new arc or focus — every action is draggable between arcs and focuses, with the board's drag feel.
What changed
DndContextreplaces the nested per-focus contexts (and the workspace-scope outer one). Nested contexts made cross-section drags structurally impossible; a single context with anonDragEndthat branches on focus / arc / action keeps the PROG-87 section reordering intact while letting action rows travel anywhere on the page.PATCH { arcId, parentActionId, rank };POST /api/actions/:id/movegains optionalarcId/rank(arc must belong to the target focus; rank validated) so a cross-focus drag lands where released. Both optional — the palette's focus-only move is unchanged.rankForInsert(slot into a foreign group by the board's pointer-past-middle rule) andinSubtreeOf(refuse drops into the action's own subtree before the doomed write).Drag feel (owner feedback round)
DragOverlaycard (bullet + title + capped step subtree, board-card rotation/shadow) that tracks the pointer across arc and focus sections; the in-list source dims to a ghost but keeps its sorting translate, exactly likeBoardCard.onDragOverpattern), so the target group — in any arc or focus — visibly opens the slot; the drop commits exactly what the preview shows.dropAnimation={null}, gliding the card into the committed slot. Safe now that PROG-119 (merged from main) notifies optimistic writes synchronously — verified frame-by-frame: the landed row is stationary at its final position from frame 0 after mouseup, no bounce-back.Same-group reorders, arc/focus section reorders, Tab/Shift+Tab reparenting, capture, and the board's shared
rankbehavior are all unchanged.Tests
bun run test: 180 pass (new coverage forrankForInsert,inSubtreeOf, andmoveActionoptimistic + rollback with arc/rank).e2e/outline-move.spec.ts: real-pointer drags for arc→arc and cross-focus (asserts re-key + alias + reload survival, and that the floating overlay is present mid-drag and clears after the drop settles).e2e/outline-container-reorder.spec.tsin passing — it had been red since PROG-111 (selectors still targeted the old "Reorder " grip buttons). All 5 outline e2e specs pass.main(agenda-quickadd ×1, comment-draft ×3) reproduce identically without this branch.Decision log:
docs/decisions/PROG-118.md(PROG-118 + PROG-118b); REFERENCE updated (Outline surface, move endpoint, containment rules).Board settle tween (follow-up)
The same drop animation now applies to the board:
DROP_ANIMATIONlives insrc/client/dropAnimation.ts, shared by both DragOverlay surfaces, replacing the board'sdropAnimation={null}(PROG-43's workaround for the stale-slot fly-back, moot since the board commits itscolumnsmirror synchronously inonDragEnd). The board's frame-sampling "doesn't flash back" spec passes with the tween on.Two e2e repairs surfaced by this:
outline-move.spec.tswas leaking open actions onto the shared dev board (archiving a focus doesn't close its actions), which slowly overflowed the board specs' columns until drags missed — it now cancels its actions before archiving; andboard-reorder's overlay-clear assertion is retimed for the ~180ms settle tween.