'use client';
import * as React from 'react';
import { cn } from '../../lib/utils';
import { StatusBadge } from '../badge/StatusBadge';
import { EmptyState } from '../states/EmptyState';
import type { NodeExecView } from './types';
import { NODE_STATUS_BADGE, formatDuration, isLiveStatus } from './exec-status';
export interface ExecutionTimelineProps {
/** Per-node execution overlays; rendered sorted by `executionOrder`. */
nodeExecutions: NodeExecView[];
/** Currently selected node id (row highlighted as active). */
activeNodeId?: string;
/** Called with a node id when its row is activated. */
onSelectNode?: (nodeId: string) => void;
className?: string;
}
function byExecutionOrder(a: NodeExecView, b: NodeExecView): number {
const ao = a.executionOrder ?? 0;
const bo = b.executionOrder ?? 0;
return ao - bo;
}
/**
* Vertical execution timeline: one row per node execution (ordered by
* `executionOrder`), each showing the node name, a status badge, the run
* duration, and inline error text for failed nodes. Running nodes show a
* pulsing live indicator; terminal nodes render statically.
*
* Read-only and app-agnostic — selection is surfaced via `onSelectNode`.
*/
export function ExecutionTimeline({
nodeExecutions,
activeNodeId,
onSelectNode,
className,
}: ExecutionTimelineProps) {
const ordered = React.useMemo(
() => [...nodeExecutions].sort(byExecutionOrder),
[nodeExecutions]
);
if (ordered.length === 0) {
return (
{node.error}
)}