Przeglądaj źródła

fix: mount PinnedDataBadge, it was dead code

PinnedDataBadge existed and was fully implemented but was not
imported or rendered anywhere - a node carrying pinned data had no
on-canvas indication of it, only the header's general "Pinned" tag.

Wired it through the same effect that already threads execution state
onto each node's data (WorkflowEditorPage.tsx) rather than adding a
new effect - the badge staleness fix (nodeChangeStatus/pinnedNodeData
recompute, previous commit) already keeps that data live, and reusing
the existing effect keeps this to one place that writes node.data
based on pin state, not two racing against each other.

The badge is only set for a kept pin (isPinKept), matching the
Viewing-vs-Pinning model: a pin that only exists to power Viewing mode
would never be seen anyway, since the canvas renders the read-only
snapshot instead of the live nodes while viewing.

This touches the same area that caused a render-loop crash earlier
(nodeChangeStatus recompute feeding back into a nodes-dependent
effect). Re-ran the same regression check after wiring the badge: 10
double-clicks and 5 drags on a pinned node, in both a fresh workflow
and a real 23-node workflow, zero console errors either time.
fszontagh 1 miesiąc temu
rodzic
commit
8fa8560b94

+ 11 - 0
webui/src/components/workflow/WorkflowNode.tsx

@@ -2,6 +2,8 @@ import { Handle, Position, NodeProps } from 'reactflow'
 import { Play, CheckCircle, XCircle, Loader2 } from 'lucide-react'
 import { NodeIcon } from './NodeIcon'
 import { NodeOutput, DynamicOutputs } from '../../api/workflows'
+import { PinnedDataBadge } from './PinnedDataBadge'
+import type { NodeChangeStatus } from '../../stores/executionPinStore'
 
 export interface NodeExecutionState {
   status: 'pending' | 'running' | 'completed' | 'failed' | 'skipped' | 'disabled'
@@ -54,6 +56,10 @@ export function WorkflowNode({ data, selected }: NodeProps) {
   const disabled = data.disabled === true
   const lockedBy = data.lockedBy as { username: string; kind: string } | undefined
   const execState = data.executionState as NodeExecutionState | undefined
+  // Set only for a kept pin (WorkflowEditorPage withholds it for a pin that
+  // only exists to power Viewing mode) - see executionPinStore's isPinKept.
+  const pinnedStatus = data.pinnedStatus as NodeChangeStatus | undefined
+  const hasPinnedData = data.hasPinnedData === true
   const outputs: NodeOutput[] = resolveOutputs(data.outputs, data.dynamicOutputs, data.config)
   const inputs: NodeOutput[] = data.inputs?.length
     ? data.inputs
@@ -127,6 +133,11 @@ export function WorkflowNode({ data, selected }: NodeProps) {
       } ${lockedBy ? 'ring-2 ring-indigo-400 ring-offset-1 dark:ring-offset-slate-900' : ''}`}
       style={getStatusStyle()}
     >
+      {/* Pinned-data badge: only appears once something is kept-pinned (see
+          WorkflowEditorPage's pinnedStatus wiring), and reflects the diff as
+          of the last graph change, not just the moment of pinning. */}
+      {pinnedStatus && <PinnedDataBadge status={pinnedStatus} hasData={hasPinnedData} />}
+
       {/* Input handles at top (not for triggers). A node that names more than
           one input gets a handle each, spread the way outputs are.
 

+ 9 - 2
webui/src/pages/WorkflowEditorPage.tsx

@@ -1641,7 +1641,12 @@ function WorkflowEditorInner() {
     dismissDenied()
   }, [deniedNode, dismissDenied, showToast])
 
-  // Update nodes with execution state (uses effectiveExecutionState for viewing pinned executions)
+  // Update nodes with execution state (uses effectiveExecutionState for viewing pinned executions),
+  // and with the pinned-data badge - but only for a kept pin. A pin that only
+  // exists to power Viewing mode is not shown as a badge: the canvas renders
+  // viewedNodes while viewing anyway, so this would never be seen then, and
+  // showing it for an un-kept pin would contradict the point of "kept" -
+  // that the badge means the user asked for this data to stay pinned.
   useEffect(() => {
     setNodes((nds) =>
       nds.map((n) => ({
@@ -1651,6 +1656,8 @@ function WorkflowEditorInner() {
           executionState: effectiveExecutionState.nodeStates[n.id],
           onExecute: () => executeWorkflow(),
           onExecuteTrigger: executeTrigger,
+          pinnedStatus: isPinKept ? nodeChangeStatus[n.id] : undefined,
+          hasPinnedData: isPinKept && pinnedNodeData[n.id] !== undefined,
         },
       }))
     )
@@ -1662,7 +1669,7 @@ function WorkflowEditorInner() {
         animated: executionState.status === 'running',
       }))
     )
-  }, [effectiveExecutionState.nodeStates, executionState.status, setNodes, setEdges, executeWorkflow])
+  }, [effectiveExecutionState.nodeStates, executionState.status, setNodes, setEdges, executeWorkflow, isPinKept, nodeChangeStatus, pinnedNodeData])
 
   // Update node selection visual
   useEffect(() => {