From 9fdf97f1488bb8e9e17b9fbc6fdf3f44afdd5972 Mon Sep 17 00:00:00 2001 From: stroblme Date: Thu, 20 Aug 2026 13:17:53 +0200 Subject: [PATCH] Select the edge you click in the flow editor MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit React Flow's selection changes had nowhere to go: the canvas passed edges but no onEdgesChange, so no edge ever carried .selected and both the selected-edge stroke and its rest colour were unreachable. The edges are derived from the bindings, so the selected ids are held on their own and marked on after the layout has had the array — a rebuild cannot drop the selection, and selecting cannot make the graph lay itself out again. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01SKL7sUgNWhukDEz95vSMQv --- NOTEPAD.md | 1 - frontend/src/components/Flow/FlowEditor.tsx | 31 ++++++++++++++++++++- 2 files changed, 30 insertions(+), 2 deletions(-) diff --git a/NOTEPAD.md b/NOTEPAD.md index 734e575..25fb6e2 100644 --- a/NOTEPAD.md +++ b/NOTEPAD.md @@ -20,7 +20,6 @@ should reopen it. - BUG/UI auto node placement on flows should be improved in regards to least crossing edges and a more vertical layout on mobile devices - FEAT/UI we should highlight failing nodes accordingly in the flow view to facilitate easier tracking of mis-configurations - BUG/UI when a dashboard widget is selected, the border does not cleanly draw on the left side of the widget (like it is obscured by the widget) -- BUG/UI: `FlowEditor` wires `onNodesChange` but no `onEdgesChange`, so React Flow's selection change never reaches the edge state and no edge in the flow editor ever carries `.selected`. Both `.react-flow__edge.selected { --edge-rest: var(--primary) }` and `LiveEdge`'s selected stroke are therefore unreachable; clicking an edge only opens the `EdgeInspector`. - INFRA: ensure that all the packages/ dependencies needed to run fluksio are available on arm to make this software runnable on e.g. raspbian - INFRA: merge the philosophy statement at the beginning of vision.md into the rest of the document. Dissolve the decision dates and fold the decisions into a clean structure - CHORE/INFRA: `bunx playwright test` from the host is unsafe against this stack — `app.fluksio.com` and `api.fluksio.com` resolve to production, and `--host-resolver-rules` steers only Chromium while `page.request.*` resolves through Node, so setup and teardown write to the live instance. Run the specs in a container on the `proxy` network with `--add-host` pointing both names at Traefik. diff --git a/frontend/src/components/Flow/FlowEditor.tsx b/frontend/src/components/Flow/FlowEditor.tsx index b391f33..de01d88 100644 --- a/frontend/src/components/Flow/FlowEditor.tsx +++ b/frontend/src/components/Flow/FlowEditor.tsx @@ -2,6 +2,7 @@ import { Background, BackgroundVariant, type Connection, + type EdgeChange, type Node as FlowCanvasNode, type NodeChange, ReactFlow, @@ -219,6 +220,11 @@ function FlowEditorInner({ const [selectedId, setSelectedId] = useState(null) const [paletteOpen, setPaletteOpen] = useState(false) const [inspected, setInspected] = useState(null) + // The edges are derived, so xyflow's own selection would be thrown away on + // every rebuild: the ids live here instead and are marked on at render. + const [selectedEdges, setSelectedEdges] = useState>( + () => new Set(), + ) const [rebind, setRebind] = useState(null) const [renamed, setRenamed] = useState(null) const [flowPanelOpen, setFlowPanelOpen] = useState(false) @@ -423,6 +429,20 @@ function FlowEditorInner({ [onNodesChange], ) + /** Selection is all an edge change can carry here: nothing else is stored. */ + const onEdgesChange = useCallback((changes: EdgeChange[]) => { + const selections = changes.filter((change) => change.type === "select") + if (!selections.length) return + setSelectedEdges((current) => { + const next = new Set(current) + for (const change of selections) { + if (change.selected) next.add(change.id) + else next.delete(change.id) + } + return next + }) + }, []) + // Dashboards and other flows wired into this one. They are drawn but never // stored: they join at render, after everything that reads or writes // canvasNodes, so an autosave, an undo or a delete cannot reach them. @@ -485,6 +505,14 @@ function FlowEditorInner({ [external, positions], ) + // Marked after the layout has had the derived array, so selecting an edge + // never sends the graph through the layout again. + const shownEdges = useMemo( + () => + edges.map((edge) => ({ ...edge, selected: selectedEdges.has(edge.id) })), + [edges, selectedEdges], + ) + const shownNodes = useMemo( () => [ ...renderedNodes.map((node) => ({ @@ -911,8 +939,9 @@ function FlowEditorInner({ > deleteNodes(deleted.filter(isDocumentNode).map((node) => node.id)) }