From b4be8bc5354fbd3a47c267c331aaf603c0f90e6e Mon Sep 17 00:00:00 2001 From: Guilhem Date: Thu, 10 Sep 2026 16:37:10 +0200 Subject: [PATCH] fix(frontend): clear the flow graph selection through xyflow's store (#11056) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Selecting a step in the flow editor sometimes opened the Settings panel instead of the step that was clicked. SelectionManager.selectId() clears xyflow's own selection before setting ours, and that clear was wired to clearFlowSelection(), which resets offsetNodeCache and reassigns nodes. That hands xyflow node objects it does not recognise, which is what makes it drop the flag — but it also makes it re-create every node's DOM: 126 elements on a 25-node flow, on every selection. Graph nodes select on pointerdown. A click is dispatched on the closest common ancestor of its pointerdown and pointerup targets, so when the release lands in that teardown gap the browser hit-tests to the pane, the click addresses the pane, and onpaneclick clears the selection back to the settings sentinel. Clear through store.unselectNodesAndEdges() instead, so no node object changes identity and there is no gap to fall into. clearFlowSelection keeps its two group-creation callers, which rebuild the graph anyway. Claude-Session: https://claude.ai/code/session_01R7caEKonPr2DmvsWRR6bCK Co-authored-by: Claude Opus 5 (1M context) --- frontend/src/lib/components/graph/FlowGraphV2.svelte | 8 +++----- .../src/lib/components/graph/SelectionTool.svelte | 12 +++++++----- frontend/src/lib/components/graph/graphContext.ts | 2 ++ 3 files changed, 12 insertions(+), 10 deletions(-) diff --git a/frontend/src/lib/components/graph/FlowGraphV2.svelte b/frontend/src/lib/components/graph/FlowGraphV2.svelte index 74430cffde..184c4e2170 100644 --- a/frontend/src/lib/components/graph/FlowGraphV2.svelte +++ b/frontend/src/lib/components/graph/FlowGraphV2.svelte @@ -734,11 +734,9 @@ return false } - // Clear SvelteFlow's internal selection by creating new nodes array function clearFlowSelection() { - // xyflow owns `selected` on the objects it was handed, and drops it only when it sees a - // node it does not recognise. Serving the cached mapping back would hand it the very - // object it marked selected, so the clear has to go through fresh objects. + // Resetting the cache and reassigning `nodes` hands xyflow objects it has not seen, the + // only lever on its selection available from our own array. offsetNodeCache = new WeakMap() nodes = nodes.map((node) => { if (node.selected) { @@ -1411,7 +1409,7 @@ /> - + {#if leftHeader}
diff --git a/frontend/src/lib/components/graph/SelectionTool.svelte b/frontend/src/lib/components/graph/SelectionTool.svelte index dd5033bf55..5fd6147c71 100644 --- a/frontend/src/lib/components/graph/SelectionTool.svelte +++ b/frontend/src/lib/components/graph/SelectionTool.svelte @@ -4,16 +4,18 @@ import type { SelectionManager } from './selectionUtils.svelte' interface Props { selectionManager: SelectionManager - clearGraphSelection: () => void } - let { selectionManager, clearGraphSelection }: Props = $props() + let { selectionManager }: Props = $props() - untrack(() => selectionManager).setClearGraphSelection(untrack(() => clearGraphSelection)) - - // Get store to access selectionRect const store = useStore() + // Clear through xyflow's store, never by handing it fresh node objects: replacing them + // re-creates every node's DOM, and a click whose node is rebuilt between pointerdown and + // release retargets to the pane, which clears the selection that same gesture just made. + // While xyflow holds a selection, useOnSelectionChange below re-broadcasts it over ours. + untrack(() => selectionManager).setClearGraphSelection(() => store.unselectNodesAndEdges()) + // Handle selection changes from SvelteFlow useOnSelectionChange(({ nodes: selectedNodes, edges: _selectedEdges }) => { // Notes are already non-selectable, so no filtering needed diff --git a/frontend/src/lib/components/graph/graphContext.ts b/frontend/src/lib/components/graph/graphContext.ts index a6245769cf..567bfb6348 100644 --- a/frontend/src/lib/components/graph/graphContext.ts +++ b/frontend/src/lib/components/graph/graphContext.ts @@ -12,6 +12,8 @@ export type GraphContext = { showAssets: Writable noteManager?: NoteManager moveManager?: MoveManager + /** Clears xyflow's selection by replacing every node object, so only for callers that rebuild + * the graph anyway. A selection change must use `selectionManager` instead. */ clearFlowSelection?: () => void yOffset?: number diffManager: FlowDiffManager