feat: simplify paths

This commit is contained in:
2026-03-14 07:52:34 +01:00
parent 353c2db335
commit b642a7aab2
18 changed files with 541 additions and 506 deletions

View File

@@ -1,85 +1,47 @@
/**
* Node lifecycle: contract that nodes implement so the graph can show the right
* connection status (edge colors) and path animation.
* Node lifecycle: nodes report error state so the graph can show the right
* connection status (edge colors). "Updating" is time-bound from triggers, not per-node.
*
* ## State flow
*
* Nodes report lifecycle (updating / error / paused) via useSyncConnectionStatus(id, state).
* This hook dispatches to the canvas store; edges read path state via selectors.
*
* ## Lifecycle phases (conceptual)
*
* - **Idle** Node is not on an active path; edges use default style.
* - **Trigger** Node's output changed; path is computed from triggers + updating nodes.
* - **Updating** Node is doing async work. Report `updating: true` → false. Path edges show blue.
* - **Paused** Node is on hold (e.g. manual mode waiting for Run). Report `paused: true` → false. Segment shows yellow.
* - **Error** Node has an error. Report `error: true` → false. Incoming edges show red.
*
* Priority for edge status: error > paused > updating > default.
* Priority for edge status: error > updating (pulse) > default.
*/
import { useEffect, useRef } from 'react'
import { dispatchCanvasCommand } from '@/app/canvas/canvasStore'
export type NodeLifecyclePhase = 'idle' | 'trigger' | 'updating' | 'paused' | 'error'
export type NodeLifecyclePhase = 'idle' | 'trigger' | 'on-path' | 'error'
/**
* State that drives connection status for this node.
* Pass the current values from your node; the hook syncs them to the canvas store.
* Only error is synced to the path; updating is shown via time-bound pulse from triggers.
*/
export type NodeConnectionStatusState = {
/** Node is doing async work (e.g. loading, running). Incoming/outgoing path edges show blue. */
/** Ignored for path; kept for API compatibility (e.g. loading spinner). */
updating?: boolean
/** Node has an error. Incoming edges to this node show red. */
error?: boolean
/** Node is on hold (e.g. agent waiting for Run). Path edges in paused segment show yellow. */
paused?: boolean
}
/**
* Syncs this node's lifecycle state to the canvas store so connection status (edge colors)
* and path animation are correct. Call once per node with the current updating/error/paused
* state; the hook will dispatch path commands to add/remove this node from the appropriate sets.
*
* Use in any node that can be updating, in error, or paused:
*
* const [loading, setLoading] = useState(false)
* const [error, setError] = useState(null)
* const hasPendingInputs = ...
* useSyncConnectionStatus(id, { updating: loading, error: !!error, paused: hasPendingInputs })
* Syncs this node's error state to the canvas store so connection status (edge colors)
* is correct. Call once per node: useSyncConnectionStatus(id, { error: !!error }).
*/
export function useSyncConnectionStatus(
nodeId: string,
state: NodeConnectionStatusState
): void {
const { updating, error, paused } = state
const prevRef = useRef({ updating: false, error: false, paused: false })
const { error } = state
const prevRef = useRef(false)
useEffect(() => {
const prev = prevRef.current
const nowUpdating = Boolean(updating)
const nowError = Boolean(error)
const nowPaused = Boolean(paused)
if (prev.updating !== nowUpdating) {
if (nowUpdating) dispatchCanvasCommand({ type: 'path/startUpdate', payload: nodeId })
else dispatchCanvasCommand({ type: 'path/endUpdate', payload: nodeId })
prev.updating = nowUpdating
}
if (prev.error !== nowError) {
if (prevRef.current !== nowError) {
dispatchCanvasCommand({ type: 'path/setError', payload: { nodeId, error: nowError } })
prev.error = nowError
prevRef.current = nowError
}
if (prev.paused !== nowPaused) {
dispatchCanvasCommand({ type: 'path/setPaused', payload: { nodeId, paused: nowPaused } })
prev.paused = nowPaused
}
return () => {
if (prevRef.current.updating) dispatchCanvasCommand({ type: 'path/endUpdate', payload: nodeId })
if (prevRef.current.error) dispatchCanvasCommand({ type: 'path/setError', payload: { nodeId, error: false } })
if (prevRef.current.paused) dispatchCanvasCommand({ type: 'path/setPaused', payload: { nodeId, paused: false } })
prevRef.current = { updating: false, error: false, paused: false }
if (prevRef.current) {
dispatchCanvasCommand({ type: 'path/setError', payload: { nodeId, error: false } })
prevRef.current = false
}
}
}, [nodeId, updating, error, paused])
}, [nodeId, error])
}