Let a node's failure outlive the run that followed it
A node's error cleared the moment it ran again, so a failure that genuinely fired an alert could leave no trace on the canvas by the time anyone looked. The engine records it now — on the node's status, so it survives a reload and every client agrees — and reading the traceback is what clears it. The seam is the event bus, which is where every failing path already meets: a queued live run, an explicit run, a preview, and a single triggered node all publish `node_error`, while the controller's own observer would have seen only one of them. That was half the confusion. The other half: clicking a failed neuron on Home often landed on a flow where everything looked fine. Nodes merge into one neuron by instance key — every InfluxDB node pointing at the same bucket is one neuron — and the click went to whichever flow contributed a member first, not the one that failed. It now goes to the failing member and selects it, and the canvas marks a failing node rather than leaving it to the dot alone. The inject node emitted one payload to every port it declared, whatever their types, so an inject on a bool port carrying the text "true" raised at publish time. Each port gets its own field now, typed and parsed by that port's dtype, and remembers what it last sent. A port that is renamed carries its value with it; one that is removed takes its value with it. An inject written before this keeps emitting exactly what it did. The derived-cron chip also appeared on the delay node, where `interval` is a rate limit and a schedule derived from it means nothing. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Uq8mtNb97A7praJLyeEYgs
This commit is contained in:
@@ -192,9 +192,12 @@ function uniqueNodeId(existing: NodeDef_Input[], type: string): string {
|
||||
|
||||
function FlowEditorInner({
|
||||
flowName,
|
||||
focus,
|
||||
onReload,
|
||||
}: {
|
||||
flowName: string
|
||||
/** A node to arrive on, from the address bar. */
|
||||
focus?: string
|
||||
onReload: () => void
|
||||
}) {
|
||||
const navigate = useNavigate()
|
||||
@@ -224,7 +227,7 @@ function FlowEditorInner({
|
||||
const [canvasNodes, setCanvasNodes, onNodesChange] = useUnpositionedNodes(
|
||||
detail.definition.nodes ?? [],
|
||||
)
|
||||
const [selectedId, setSelectedId] = useState<string | null>(null)
|
||||
const [selectedId, setSelectedId] = useState<string | null>(focus ?? 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
|
||||
@@ -562,6 +565,14 @@ function FlowEditorInner({
|
||||
])
|
||||
}, [key, direction, external, updateNodeInternals])
|
||||
|
||||
// The canvas is not remounted per node — that would throw the session away
|
||||
// on every click in the brain graph — so arriving at a flow already open
|
||||
// only changes the address. Seeded above for the first arrival, set here for
|
||||
// the ones after it.
|
||||
useEffect(() => {
|
||||
if (focus) setSelectedId(focus)
|
||||
}, [focus])
|
||||
|
||||
const selected = definitions.find((node) => node.id === selectedId) ?? null
|
||||
// A panel is the view you are working in, so it takes the room — but never
|
||||
// the lanes the bars sit in: publishing is most wanted right after editing.
|
||||
@@ -1365,7 +1376,14 @@ function useUnpositionedNodes(definitions: NodeDef_Input[]) {
|
||||
return useNodesState<FlowCanvasNode>(toCanvasNodes(definitions))
|
||||
}
|
||||
|
||||
export function FlowEditor({ flowName }: { flowName: string }) {
|
||||
export function FlowEditor({
|
||||
flowName,
|
||||
focus,
|
||||
}: {
|
||||
flowName: string
|
||||
/** A node to select on arrival — see `FlowEditorInner`. */
|
||||
focus?: string
|
||||
}) {
|
||||
const navigate = useNavigate()
|
||||
const onAuthFailure = useCallback(() => {
|
||||
navigate({ to: "/login" })
|
||||
@@ -1383,10 +1401,14 @@ export function FlowEditor({ flowName }: { flowName: string }) {
|
||||
* Remounting per flow keeps canvas state from leaking between them, and
|
||||
* it is what makes `fitView` run once per flow: xyflow queues the fit on
|
||||
* mount and resolves it as soon as the nodes have been measured.
|
||||
* Deliberately not per focused node — that is a selection, not another
|
||||
* document, and remounting the canvas on every click in the brain graph
|
||||
* would throw an unsaved edit away with it.
|
||||
*/}
|
||||
<FlowEditorInner
|
||||
key={`${flowName}:${epoch}`}
|
||||
flowName={flowName}
|
||||
focus={focus}
|
||||
onReload={reload}
|
||||
/>
|
||||
</ReactFlowProvider>
|
||||
|
||||
Reference in New Issue
Block a user