Skip to content

feat(outline): PROG-118 drag actions between arcs and focuses#96

Merged
bryankennedy merged 8 commits into
mainfrom
act/PROG-118
Jul 14, 2026
Merged

feat(outline): PROG-118 drag actions between arcs and focuses#96
bryankennedy merged 8 commits into
mainfrom
act/PROG-118

Conversation

@exe-dev-github-integration

@exe-dev-github-integration exe-dev-github-integration Bot commented Jul 13, 2026

Copy link
Copy Markdown
Contributor

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

  • One page-wide DndContext replaces the nested per-focus contexts (and the workspace-scope outer one). Nested contexts made cross-section drags structurally impossible; a single context with an onDragEnd that branches on focus / arc / action keeps the PROG-87 section reordering intact while letting action rows travel anywhere on the page.
  • Drop semantics — "it lands where you dropped it":
    • onto a row in another group (same focus): joins that group at the drop spot — one optimistic PATCH { arcId, parentActionId, rank };
    • onto an arc section header/empty body: appends to that arc's top level;
    • into another focus's section (workspace scope): a real move (re-key + alias, steps detach — PROG-102/PROG-124), landing top-level at the drop spot.
  • POST /api/actions/:id/move gains optional arcId/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.
  • New unit-tested helpers: rankForInsert (slot into a foreign group by the board's pointer-past-middle rule) and inSubtreeOf (refuse drops into the action's own subtree before the doomed write).

Drag feel (owner feedback round)

  • Floating card in hand: a held row is carried by a DragOverlay card (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 like BoardCard.
  • Live landing preview: hovering a different sibling group re-homes the row there in the rendered list (the board's PROG-59 onDragOver pattern), so the target group — in any arc or focus — visibly opens the slot; the drop commits exactly what the preview shows.
  • Settle on drop: the default ~180ms drop tween replaces 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 rank behavior are all unchanged.

Tests

  • bun run test: 180 pass (new coverage for rankForInsert, inSubtreeOf, and moveAction optimistic + rollback with arc/rank).
  • New 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).
  • Repaired e2e/outline-container-reorder.spec.ts in passing — it had been red since PROG-111 (selectors still targeted the old "Reorder " grip buttons). All 5 outline e2e specs pass.
  • Pre-existing, unrelated e2e failures on 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_ANIMATION lives in src/client/dropAnimation.ts, shared by both DragOverlay surfaces, replacing the board's dropAnimation={null} (PROG-43's workaround for the stale-slot fly-back, moot since the board commits its columns mirror synchronously in onDragEnd). The board's frame-sampling "doesn't flash back" spec passes with the tween on.

Two e2e repairs surfaced by this: outline-move.spec.ts was 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; and board-reorder's overlay-clear assertion is retimed for the ~180ms settle tween.

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.
@bryankennedy
bryankennedy merged commit 96f3802 into main Jul 14, 2026
2 checks passed
@bryankennedy
bryankennedy deleted the act/PROG-118 branch July 14, 2026 17:54
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant