refactoring: graph state

This commit is contained in:
2026-03-12 17:40:05 +01:00
parent bfd1a40332
commit 4bd2f1b458
9 changed files with 176 additions and 58 deletions

View File

@@ -2,9 +2,8 @@
* Connection status: visual state of an edge (color/class).
* Priority when multiple apply: error > paused > updating > default.
*
* Status is derived from node lifecycle state: nodes report updating / paused / error
* via useSyncConnectionStatus() in nodeLifecycle.ts, which updates FlowContext sets.
* Edges read those sets here to pick the single status per edge.
* State flow: nodes report lifecycle via useSyncConnectionStatus() (nodeLifecycle.ts) → FlowContext
* holds the sets → edges pass those sets into getConnectionStatus() here. See lib/graph/state.ts.
*/
export type ConnectionStatus = 'default' | 'updating' | 'paused' | 'error'

View File

@@ -1,3 +1,20 @@
/**
* FlowContext provides graph state, connection path state, and UI state to nodes and edges.
*
* ## 1. Graph state (nodes, edges)
* - nodes, setNodes, edges, setEdges owned by useCanvasGraph (history + persistence).
*
* ## 2. Connection path state (edge visuals and path animation)
* - Nodes report lifecycle (updating / error / paused) via useSyncConnectionStatus() in nodeLifecycle.
* - Context holds: connectionPathUpdatingNodeIds, connectionPathTriggerNodeIds, connectionPathNodeIds,
* connectionPathPausedSegmentNodeIds, connectionPathActiveSegmentNodeIds, connectionPathPausedNodeIds,
* connectionPathErrorNodeIds, plus add/remove/start/end callbacks.
* - Edges use these in getConnectionStatus() for color (default / updating / paused / error). See connectionStatus.ts.
*
* ## 3. UI state
* - renamingNodeId, fullscreenNodeId, connectionFrom (drag-from handle), flowActionsRef, isValidConnection.
*/
import React, { useMemo } from 'react'
import type { Connection } from '@xyflow/react'
import type { AppNode, AppEdge } from './nodeTypes'
@@ -13,48 +30,36 @@ export type FlowActions = {
}
export type FlowContextValue = {
// ---- Graph state ----
nodes: AppNode[]
setNodes: (updater: AppNode[] | ((prev: AppNode[]) => AppNode[])) => void
edges: AppEdge[]
setEdges: (updater: AppEdge[] | ((prev: AppEdge[]) => AppEdge[])) => void
// ---- UI state ----
renamingNodeId: string | null
setRenamingNodeId: (id: string | null) => void
/** Set when user starts dragging from an output handle; cleared on connect end. Used to highlight valid targets. */
connectionFrom: ConnectionFrom
setConnectionFrom: (v: ConnectionFrom) => void
isValidConnection: (connection: Connection) => boolean
/** Set by FlowKeyboardShortcuts so Node menubar can trigger paste / fit view. */
flowActionsRef: React.MutableRefObject<FlowActions | null>
/** When set, graph centers on this node and a fullscreen dialog shows the node. Cleared on close. */
fullscreenNodeId: string | null
setFullscreenNodeId: (id: string | null) => void
/** Node ids currently updating (e.g. render loading, agent running). Only edges on those paths show the ant trail. */
// ---- Connection path state (edge status and path animation; see state.ts ConnectionPathState) ----
connectionPathUpdatingNodeIds: string[]
/** Node ids that triggered the current update (e.g. variable/config that changed). Path is restricted to downstream(trigger) ∩ upstream(updating). */
connectionPathTriggerNodeIds: string[]
/** Call when this node's output changed and may trigger downstream updates (e.g. variable value, config content). */
addConnectionPathTrigger: (nodeId: string) => void
/** All node ids on the path of an update. Edges with both endpoints in this set animate. */
connectionPathNodeIds: Set<string>
/** Path nodes in the "paused" segment (from trigger up to Archon on hold). Those edges are yellow. */
connectionPathPausedSegmentNodeIds: Set<string>
/** Path nodes not in the paused segment (downstream of pause). Only those edges are blue (updating). */
connectionPathActiveSegmentNodeIds: Set<string>
/** Nodes that are on hold (e.g. Renderer in manual mode waiting for Run). */
connectionPathPausedNodeIds: string[]
/** Add this node as paused (on hold); remove when user continues. */
addConnectionPathPausedNode: (nodeId: string) => void
/** Remove this node from paused. */
removeConnectionPathPausedNode: (nodeId: string) => void
/** Node ids that have an error (e.g. Render node). Incoming edges show error status (red). */
connectionPathErrorNodeIds: string[]
/** Call when this node has an error; remove when error is cleared. */
addConnectionPathError: (nodeId: string) => void
/** Call when this node's error is cleared. */
removeConnectionPathError: (nodeId: string) => void
/** Call when a path update starts for this node. Animation runs at least CONNECTION_PATH_UPDATE_MIN_MS. */
startConnectionPathUpdate: (nodeId: string) => void
/** Call when a path update ends for this node. If min duration not reached, animation continues until then. */
endConnectionPathUpdate: (nodeId: string) => void
}

View File

@@ -2,30 +2,21 @@
* Node lifecycle: contract that nodes implement so the graph can show the right
* connection status (edge colors) and path animation.
*
* ## State flow
*
* Nodes report lifecycle (updating / error / paused) via useSyncConnectionStatus(id, state).
* This hook updates FlowContext sets; edges read them via getConnectionStatus() in connectionStatus.ts.
* See lib/graph/state.ts for the overall state flow (graph state, connection path state).
*
* ## Lifecycle phases (conceptual)
*
* - **Idle** Node is not on an active path; edges to/from it use default style.
* - **Trigger** Node's output changed (e.g. config content, variable value). Reported
* automatically when the node calls `updateData()` from useAbstractNode. Downstream
* path is computed from triggers + updating nodes.
* - **Updating** Node is doing async work (e.g. renderer loading, agent run triggered by renderer).
* Report `updating: true` at start, `updating: false` when done. Incoming/outgoing
* edges on the path show "updating" (blue).
* - **Paused** Node is on hold (e.g. renderer in manual mode waiting for Run after inputs changed).
* Report `paused: true` when waiting, `paused: false` when not. Edges in the paused
* segment show "paused" (yellow).
* - **Error** Node has an error to show. Report `error: true` when error is set,
* `error: false` when cleared. Incoming edges to this node show "error" (red).
* - **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.
*
* ## Connection status integration
*
* Edge status is derived in getConnectionStatus() from the sets that this lifecycle
* feeds: connectionPathUpdatingNodeIds, connectionPathPausedNodeIds,
* connectionPathErrorNodeIds (plus path/paused segment from graphPath). Priority:
* error > paused > updating > default.
*
* Nodes that can be updating, paused, or in error should call useSyncConnectionStatus()
* with their current state so edges update correctly.
* Priority for edge status: error > paused > updating > default.
*/
import { useContext, useEffect, useRef } from 'react'

View File

@@ -0,0 +1,87 @@
/**
* Central state contracts for the graph: in-memory graph, connection path (edge visuals),
* and node display status. Use these types to keep state flow predictable and readable.
*
* ## State flow (high level)
*
* 1. **Graph state** (nodes, edges) Owned by useGraphStateWithHistory, persisted by useCanvasGraph.
* Updates via setNodes/setEdges (with undo) or setNodesSilent/setEdgesSilent (no history).
*
* 2. **Connection path state** Owned by useCanvasConnectionPath, provided via FlowContext.
* Nodes report lifecycle (updating / error / paused) via useSyncConnectionStatus; the hook
* updates context sets. Edges read those sets and getConnectionStatus() to decide color (default / updating / paused / error).
*
* 3. **Node display state** Each node type derives its own UI state (e.g. loading, success, error, initial).
* Rendering node uses displayStatus from useRenderingNodeState; other nodes can use getNodeDisplayStatus().
*/
import type { AppNode, AppEdge } from './nodeTypes'
// ---------------------------------------------------------------------------
// Graph state (in-memory)
// ---------------------------------------------------------------------------
/** In-memory graph: nodes and edges. Updated via setNodes/setEdges; history in useGraphStateWithHistory. */
export type GraphState = {
nodes: AppNode[]
edges: AppEdge[]
}
// ---------------------------------------------------------------------------
// Connection path state (edge visuals and path animation)
// ---------------------------------------------------------------------------
/**
* Slice of FlowContext that drives edge status and path animation.
* Nodes report updating/error/paused via useSyncConnectionStatus; this state is derived from those reports.
* Edges use pathNodeIds, pausedSegmentNodeIds, activeSegmentNodeIds, errorTargetNodeIds in getConnectionStatus().
*/
export type ConnectionPathState = {
/** Node ids currently doing async work (e.g. render loading). Path edges show "updating" (blue). */
connectionPathUpdatingNodeIds: string[]
/** Node ids that triggered the update (e.g. config changed). Path = downstream(trigger) ∩ upstream(updating). */
connectionPathTriggerNodeIds: string[]
/** All node ids on the path. Edges with both endpoints here animate. */
connectionPathNodeIds: Set<string>
/** Path nodes in the paused segment (yellow edges). */
connectionPathPausedSegmentNodeIds: Set<string>
/** Path nodes in the active segment (blue edges). */
connectionPathActiveSegmentNodeIds: Set<string>
/** Node ids on hold (e.g. render in manual mode). */
connectionPathPausedNodeIds: string[]
/** Node ids with error (incoming edges show red). */
connectionPathErrorNodeIds: string[]
}
// ---------------------------------------------------------------------------
// Node display status (for UI: status indicator, empty state)
// ---------------------------------------------------------------------------
/** Status for node UI: what to show in NodeStatusIndicator and empty/error states. */
export type NodeDisplayStatus = 'initial' | 'loading' | 'success' | 'error'
/**
* Derive display status from node state. Use in nodes to drive NodeStatusIndicator and empty/error UI.
* Priority: loading > error > hasContent (success) > initial.
*/
export function getNodeDisplayStatus(state: {
loading: boolean
error: unknown
hasContent: boolean
}): NodeDisplayStatus {
if (state.loading) return 'loading'
if (state.error != null) return 'error'
if (state.hasContent) return 'success'
return 'initial'
}
// ---------------------------------------------------------------------------
// Stored graph state (persistence)
// ---------------------------------------------------------------------------
/** Shape of graph state when saved/loaded (e.g. localStorage). version for future migrations. */
export type StoredGraphState = {
version: number
nodes: unknown[]
edges: unknown[]
}