a3s-flow 1.1.0

Durable workflow engine and Rust SDK for A3S
Documentation
import {
  CheckCircle,
  ClockCounterClockwise,
  Code,
  ListBullets,
  X,
} from '@phosphor-icons/react';
import type { WorkflowPlaygroundCopy } from './WorkflowPlayground.copy';
import type { PlaygroundDebugTab } from './WorkflowPlaygroundChrome';

export type PlaygroundRunStep = {
  nodeId: string;
  label: string;
  type: string;
  durationMs: number;
};

export type PlaygroundRunRecord = {
  id: string;
  startedAt: string;
  durationMs: number;
  steps: PlaygroundRunStep[];
};

type WorkflowPlaygroundDebugProps = {
  copy: WorkflowPlaygroundCopy;
  open: boolean;
  activeTab: PlaygroundDebugTab;
  onTabChange: (tab: PlaygroundDebugTab) => void;
  onClose: () => void;
  trace: readonly PlaygroundRunStep[];
  runningNodeId?: string;
  variables: Readonly<Record<string, string>>;
  history: readonly PlaygroundRunRecord[];
  onSelectNode: (nodeId: string) => void;
};

export function WorkflowPlaygroundDebug({
  copy,
  open,
  activeTab,
  onTabChange,
  onClose,
  trace,
  runningNodeId,
  variables,
  history,
  onSelectNode,
}: WorkflowPlaygroundDebugProps) {
  if (!open) return null;

  const tabs: ReadonlyArray<{
    id: PlaygroundDebugTab;
    label: string;
    icon: typeof ListBullets;
  }> = [
    { id: 'trace', label: copy.trace, icon: ListBullets },
    { id: 'variables', label: copy.cachedVariables, icon: Code },
    { id: 'history', label: copy.runHistory, icon: ClockCounterClockwise },
  ];

  return (
    <section
      aria-label={copy.trace}
      className="a3s-debug-panel"
      data-testid="debug-panel"
    >
      <header>
        <nav aria-label={copy.trace}>
          {tabs.map(({ id, label, icon: Icon }) => (
            <button
              aria-pressed={activeTab === id}
              className={activeTab === id ? 'is-active' : undefined}
              key={id}
              onClick={() => onTabChange(id)}
              type="button"
            >
              <Icon aria-hidden="true" />
              {label}
            </button>
          ))}
        </nav>
        <button
          aria-label={copy.close}
          onClick={onClose}
          title={copy.close}
          type="button"
        >
          <X aria-hidden="true" />
        </button>
      </header>

      {activeTab === 'trace' && (
        <div className="a3s-debug-trace">
          <div className="a3s-debug-trace__list">
            {trace.length === 0 && !runningNodeId && <p>{copy.noTrace}</p>}
            {trace.map((step) => (
              <button
                key={step.nodeId}
                onClick={() => onSelectNode(step.nodeId)}
                type="button"
              >
                <CheckCircle aria-hidden="true" weight="fill" />
                <span>
                  <strong>{step.label}</strong>
                  <small>{step.type}</small>
                </span>
                <time>{step.durationMs} ms</time>
              </button>
            ))}
            {runningNodeId &&
              !trace.some(({ nodeId }) => nodeId === runningNodeId) && (
                <div className="a3s-debug-trace__running" role="status">
                  <i aria-hidden="true" />
                  <span>{copy.run}</span>
                </div>
              )}
          </div>
          <div className="a3s-debug-trace__detail">
            <p>{copy.localRun}</p>
          </div>
        </div>
      )}

      {activeTab === 'variables' && (
        <div className="a3s-debug-variables">
          {Object.entries(variables).map(([key, value]) => (
            <label className="is-readonly" key={key}>
              <span>
                <code>{key}</code>
                <small>string</small>
              </span>
              <input aria-label={key} readOnly value={value} />
            </label>
          ))}
        </div>
      )}

      {activeTab === 'history' && (
        <div className="a3s-debug-history">
          {history.length === 0 ? (
            <p>{copy.noHistory}</p>
          ) : (
            history.map((run) => (
              <article key={run.id}>
                <CheckCircle aria-hidden="true" weight="fill" />
                <div>
                  <strong>{run.id}</strong>
                  <time>{run.startedAt}</time>
                </div>
                <span>{run.steps.length}</span>
                <b>{run.durationMs} ms</b>
              </article>
            ))
          )}
        </div>
      )}
    </section>
  );
}