fix(editor): unify wall-endpoint move activation; stop wall-move from co-firing

Two fixes to the wall-endpoint reshape interaction.

selection-manager: a node:click is synthesized on R3F pointer-up, so an endpoint
handle that sits on the wall body lets the wall mesh (raycast-hit behind it from
a 3D angle) emit its own click on the same release — selecting the wall and
arming its move tool on top of the endpoint move. Ignore the body click while an
`endpoint` reshape owns the pointer. Scoped to `endpoint` so hole-edit (which
relies on node clicks to exit) is unaffected.

move-endpoint-tool: a press-drag committed on release but a tap dismissed, and
whether the tap's release ran at all raced the window pointer-up listener
mounting a tick after the handle's pointerdown ("works once, then needs a long
press"). Unify on one rule: commit only when the endpoint actually moved,
otherwise stay armed. A tap now grabs the endpoint (it follows the cursor; the
next click after a move commits) exactly like a press-drag — both engage
identically. Drops the now-dead hasDraggedRef.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Wassim SAMAD
2026-06-27 20:08:17 -04:00
co-authored by Claude Opus 4.8
parent eeadec11a7
commit 5f603e83c4
2 changed files with 20 additions and 10 deletions
@@ -1418,6 +1418,16 @@ export const SelectionManager = () => {
// Skip if box-select just completed (drag ended over a node)
if (boxSelectHandled) return
// node:click is synthesized on pointer-up (use-node-events). A wall/fence
// endpoint handle sits ON the wall body, so from a 3D angle the wall mesh
// is raycast-hit behind it and the SAME pointer-up also emits the wall's
// click — which would select + arm the wall move tool on top of the
// endpoint move. While an endpoint reshape owns the pointer, ignore the
// body click so only the reshape tool handles the release. (Scoped to
// `endpoint`: hole-edit relies on node clicks to exit, just below.)
const activeScope = useInteractionScope.getState().scope
if (activeScope.kind === 'reshaping' && activeScope.reshape === 'endpoint') return
const node = event.node
// A ceiling is selectable only through its corner handles, never via