Select the edge you click in the flow editor

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) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SKL7sUgNWhukDEz95vSMQv
This commit is contained in:
2026-08-20 13:18:13 +02:00
co-authored by Claude Opus 5
parent 9c6c106065
commit f719816992
2 changed files with 30 additions and 2 deletions
-1
View File
@@ -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.
+30 -1
View File
@@ -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<string | null>(null)
const [paletteOpen, setPaletteOpen] = useState(false)
const [inspected, setInspected] = useState<InspectedEdge | null>(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<ReadonlySet<string>>(
() => new Set(),
)
const [rebind, setRebind] = useState<Rebind | null>(null)
const [renamed, setRenamed] = useState<MessageRename | null>(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({
>
<ReactFlow
nodes={shownNodes}
edges={edges}
edges={shownEdges}
onNodesChange={trackMeasured}
onEdgesChange={onEdgesChange}
onNodesDelete={(deleted) =>
deleteNodes(deleted.filter(isDocumentNode).map((node) => node.id))
}