Comfy-Org / Comfy-Org/ComfyUI_frontend

Vue node drag applies nothing for gestures with fewer than three pointermove events

Open
#15,351 1 comment 1 reaction 1 assignee Claimed by @DrJKL View on GitHub
area:nodes area:vue-migration Potential Bug
Dominant language
TypeScript
Stars
2k
Forks
704
Avg merge
1d 8h
Merged PRs (30d)
512

Description

## What happens

A Vue-node drag applies **nothing** unless the gesture carries at least three `pointermove` events. One or two moves leave the node at its original position — not a partial move, zero — even if the pointer button stays held for hundreds of milliseconds afterwards.

Measured on the cloud build (1280x720, `Comfy.VueNodes.Enabled: true`, template `image_krea2_turbo_t2i`, node 30, node box 327x608, grab point 60px into its title bar):

| gesture | `page.mouse.move(..., { steps })` | node moved |
| --- | --- | --- |
| +120,+80 | 1 | `0, 0` |
| +120,+80 | 2 | `0, 0` |
| +120,+80 | 3 | `120, 80` |
| +220,+140 | 1 | `0, 0` |
| +220,+140 | 12 | `220, 140` |

Holding the button down after the single move does not rescue it:

```
down → move(+120,+80) → wait 400ms → node moved 0,0 → up → wait 400ms → node moved 0,0
```

Both destinations are inside the node box, so this is not "the pointer left the element".

## Where it is not

This was first suspected to be the first-run tour's coachmark scrim swallowing the rest of a drag once the pointer left the spotlight cutout (`coach-hit-region` in `src/platform/onboarding/TourOverlay.vue` is `pointer-events-auto` outside the hole). **It is not.** The same measurements with the tour dismissed (Escape) are identical:

```
tour open stepped +220,+140 → moved 220,140 jump +220,+140 → moved 0,0
tour closed stepped +220,+140 → moved 220,140 jump +220,+140 → moved 0,0
```

Once the drag engages, it survives the pointer leaving the cutout intact, because `useNodeDrag.handleDrag` takes pointer capture on the first move it sees.

## Leads

`src/renderer/extensions/vueNodes/layout/useNodeDrag.ts`:

- `handleDrag` batches the position update into `requestAnimationFrame` and early-returns while `rafId !== null`, so several moves inside one frame collapse into one scheduled update.
- `resetDragState`, which `endDrag` calls, does `cancelAnimationFrame(rafId)` — a scheduled update that has not run yet is dropped rather than flushed.

That pair explains a gesture that ends before its frame, but not the held case above, so the mechanism is not fully pinned.

## Impact

No known user-facing impact: a real pointer emits many moves across many frames, so a human drag always clears the threshold. It matters for synthetic input — Playwright's `page.mouse.move` defaults to `steps: 1`, so a raw drag in an e2e test silently no-ops and asserts nothing (the in-repo `ComfyMouse` helpers already pass a step count and are unaffected), and likewise for extensions that dispatch pointer events directly.

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.