Portal Community

Where Status Data Lives

The designerModeStore (injected by withNodeHooks as this.designerMode) holds a map of node execution statuses keyed by node ID. This is populated in real-time via SignalR events.

// DesignerModeStore shape (relevant fields)
interface DesignerModeStore {
  isExecutionMode: boolean;
  nodeStatuses: Record<string, NodeExecutionStatus>;
  // ...
}

type NodeExecutionStatus =
  | 'pending'
  | 'running'
  | 'completed'
  | 'failed'
  | 'skipped'
  | 'suspended'
  | 'cancelled';

Reading Status in Your Renderer

// In renderBody() of your BaseNode subclass
renderBody() {
  const { id } = this.props;
  const { isExecutionMode, nodeStatuses } = this.designerMode;

  // Only show status overlay when a workflow is running
  const status = isExecutionMode ? (nodeStatuses[id] ?? 'pending') : null;

  return (
    <>
      {/* ...handles... */}

      <div className={`node-wrapper-inner ${status ? `status-${status}` : ''}`}>
        <div className="node-header">...</div>
        <div className="node-body">...</div>
      </div>

      {/* Status border overlay */}
      {status && (
        <div className={`node-status-overlay status-border-${status}`} />
      )}
    </>
  );
}

Status Colours

StatusBadgeBorder ColorNotes
pending Default #718096 (grey) Not yet reached in execution
running Pulsing #60a5fa (blue, animated) CSS keyframe pulse animation on the border
completed Solid #34d399 (green) Execution succeeded
failed Solid #f87171 (red) Unhandled error; execution routed to error path or stopped
skipped Dimmed #718096 (grey, dashed) Branch not taken (e.g. false arm of an if node)
suspended Pulsing #fbbf24 (amber) Waiting for human approval (HIL pattern)

Shared CSS Classes

The shared Flow Studio CSS defines the overlay classes so you do not have to duplicate them:

/* From flow-studio-designer shared CSS */
.node-status-overlay {
  position: absolute;
  inset: 0;
  border-radius: inherit;
  pointer-events: none;
  border: 2px solid transparent;
  transition: border-color 0.2s;
}

.status-border-running {
  border-color: #60a5fa;
  animation: statusPulse 1.5s ease-in-out infinite;
}
.status-border-completed { border-color: #34d399; }
.status-border-failed    { border-color: #f87171; }
.status-border-suspended {
  border-color: #fbbf24;
  animation: statusPulse 2s ease-in-out infinite;
}
.status-border-skipped   { border-color: #718096; border-style: dashed; }
.status-border-pending   { border-color: #718096; opacity: 0.4; }

@keyframes statusPulse {
  0%, 100% { opacity: 1; }
  50%       { opacity: 0.4; }
}

Execution Mode Guard

Always check isExecutionMode before reading nodeStatuses. In design mode (no running execution), the status map may contain stale data from the previous execution. Displaying stale status in design mode is confusing — gate the entire overlay behind the mode check.

// Correct pattern — guard with isExecutionMode
const status = this.designerMode.isExecutionMode
  ? (this.designerMode.nodeStatuses[this.props.id] ?? 'pending')
  : null;

// Wrong — may show stale status from previous run
const status = this.designerMode.nodeStatuses[this.props.id];
Copy the Pattern from CustomNode CustomNode.tsx contains the canonical execution status overlay implementation. Copy the node-status-overlay div and the isExecutionMode guard directly — it is already tested and handles all six status values correctly.