Portal Community

BaseNode Status Overlay Pattern

// BaseNode.tsx (simplified)
import { useDesignerModeStore } from '@flow-studio/services';
import { shallow } from 'zustand/shallow';

interface BaseNodeProps {
    id   : string;
    data : WorkflowNodeData;
}

export function BaseNode({ id, data }: BaseNodeProps) {
    const { nodeStatus, currentMode } = useDesignerModeStore(
        state => ({
            nodeStatus : state.nodeStatuses[id] ?? null,
            currentMode: state.currentMode
        }),
        shallow
    );

    const showOverlay = currentMode === 'execution' && nodeStatus !== null;

    return (
        <div className={`node node-${nodeStatus?.status ?? 'default'}`}>
            <div className="node-header">{data.label}</div>
            <div className="node-body">
                {/* node-specific content (rendered by subclass) */}
            </div>
            {showOverlay && (
                <div className={`node-status-badge status-${nodeStatus.status}`}>
                    <StatusIcon status={nodeStatus.status} />
                    {nodeStatus.durationMs != null &&
                        <span>{nodeStatus.durationMs}ms</span>}
                </div>
            )}
        </div>
    );
}

Status Badge CSS Classes

/* Node border color changes based on status */
.node-success   { border-color: var(--success-color); }
.node-failed    { border-color: var(--error-color); }
.node-running   { border-color: var(--accent2); animation: pulse 1s infinite; }
.node-suspended { border-color: var(--accent3); animation: pulse-slow 2s infinite; }
.node-skipped   { border-color: var(--warning-color); opacity: 0.6; }
.node-pending   { border-color: var(--border-color); }

/* Status badge overlay (top-right corner of node) */
.node-status-badge { position: absolute; top: -8px; right: -8px; border-radius: 50%; }
.status-success    { background: var(--success-color); }
.status-failed     { background: var(--error-color); }
.status-running    { background: var(--accent2); }

Custom Node Renderer — Extending BaseNode

// Custom nodes extend BaseNode — they inherit the status overlay for free
export function ApprovalNode({ id, data }: BaseNodeProps) {
    return (
        <BaseNode id={id} data={data}>
            {/* Custom content inside the node */}
            <div className="approval-actors">
                <span>Actor: {data.config.actorId}</span>
            </div>
        </BaseNode>
    );
}

// BaseNode handles mode detection and overlay rendering
// Custom nodes do NOT need to read from designerModeStore directly
Status overlays are additive: They render on top of the existing node design. Custom node renderers do not need to check currentMode — BaseNode handles that. Custom nodes only need to render their own content; the status badge is injected automatically.