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)) }