Skip to main content
// Props
import { DecisionTable } from "smithers-orchestrator";

type DecisionTableProps = {
  id?: string;
  rules: DecisionRule[];
  default?: ReactElement; // rendered when no rule matches
  strategy?: "first-match" | "all-match"; // default "first-match"
  skipIf?: boolean;
};

type DecisionRule = {
  when: boolean; // evaluated at render time
  then: ReactElement;
  label?: string;
};
<DecisionTable
  rules={[
    {
      when: triage.severity === "critical",
      then: (
        <Task id="page-oncall" output={outputs.page} agent={pagerAgent}>
          Page the on-call engineer immediately.
        </Task>
      ),
    },
    {
      when: triage.severity === "high",
      then: <Task id="assign-senior" output={outputs.assign}>{{ assignee: "senior-pool" }}</Task>,
    },
  ]}
  default={<Task id="default-assign" output={outputs.assign}>{{ assignee: "general-pool" }}</Task>}
/>

Notes

  • first-match builds nested Branches; order matters.
  • all-match wraps every matching rule in a Parallel; no ordering guarantee.

Source

The <DecisionTable> implementation and the files it imports, straight from the package source. This section is generated; edit the source, not this block.
// @smithers-type-exports-begin
/** @typedef {import("./DecisionRule.ts").DecisionRule} DecisionRule */
/** @typedef {import("./DecisionTableProps.ts").DecisionTableProps} DecisionTableProps */
// @smithers-type-exports-end

import React from "react";
import { Branch } from "./Branch.js";
import { Parallel } from "./Parallel.js";
/**
 * Structured deterministic routing. Replaces deeply nested Branches with a
 * flat, declarative rule table.
 *
 * - `"first-match"` builds nested Branch elements so the first matching rule wins.
 * - `"all-match"` gathers all matching rules' `then` elements into a Parallel.
 *
 * Composes Branch and Parallel internally.
 * @param {DecisionTableProps} props
 */
export function DecisionTable(props) {
    if (props.skipIf)
        return null;
    const { rules, strategy = "first-match" } = props;
    if (strategy === "all-match") {
        const matching = rules.filter((r) => r.when).map((r) => r.then);
        if (matching.length === 0) {
            return props.default ?? null;
        }
        return React.createElement(Parallel, { id: props.id }, ...matching);
    }
    // "first-match": build nested Branches from the last rule backward.
    // The innermost else is the default fallback.
    let fallback = props.default ?? null;
    for (let i = rules.length - 1; i >= 0; i--) {
        const rule = rules[i];
        fallback = React.createElement(Branch, {
            if: rule.when,
            then: rule.then,
            else: fallback,
        });
    }
    return fallback;
}
import React from "react";
import { SmithersError } from "@smithers-orchestrator/errors/SmithersError";
/** @typedef {import("./BranchProps.ts").BranchProps} BranchProps */

/**
 * @param {BranchProps} props
 */
export function Branch(props) {
    // <Branch> resolves its subtree from the `then`/`else` props; any JSX children
    // would be silently dropped, removing those tasks from the graph with no
    // feedback. Fail fast instead. (Checked before skipIf so a stray-children
    // mistake still surfaces even on a skipped branch.)
    if (props.children !== undefined && props.children !== null) {
        throw new SmithersError("INVALID_INPUT", `<Branch> does not take children. Use the "then" and "else" props instead, e.g. ` +
            `<Branch if={cond} then={<Task .../>} else={<Task .../>} />. ` +
            `Children passed to <Branch> are silently ignored and would drop those tasks from the graph.`);
    }
    if (props.skipIf)
        return null;
    const chosen = props.if ? props.then : (props.else ?? null);
    // The branch is resolved to `chosen` at render time, so the host element
    // carries no props of its own (align with the sanitizing structural components).
    return React.createElement("smithers:branch", {}, chosen);
}
import React from "react";
/** @typedef {import("./ParallelProps.ts").ParallelProps} ParallelProps */

/**
 * @param {ParallelProps} props
 */
export function Parallel(props) {
    if (props.skipIf)
        return null;
    // Align prop sanitization with other structural components. `label` names
    // the phase group in run views (graph, the Claude /workflows mirror).
    const next = {
        maxConcurrency: props.maxConcurrency,
        subtreeConcurrency: props.subtreeConcurrency,
        id: props.id,
        ...(props.label === undefined ? {} : { label: props.label }),
    };
    return React.createElement("smithers:parallel", next, props.children);
}
import type React from "react";

export type DecisionRule = {
	/** Condition evaluated at render time. */
	when: boolean;
	/** Element to render when this rule matches. */
	then: React.ReactElement;
	/** Optional display label for the rule. */
	label?: string;
};
import type React from "react";
import type { DecisionRule } from "./DecisionRule.ts";

export type DecisionTableProps = {
	/** ID prefix for generated wrapper nodes. */
	id?: string;
	/** Ordered list of rules. Each rule has a `when` condition and a `then` element. */
	rules: DecisionRule[];
	/** Fallback element rendered when no rules match. */
	default?: React.ReactElement;
	/** `"first-match"` (default): first matching rule wins. `"all-match"`: all matching rules run in parallel. */
	strategy?: "first-match" | "all-match";
	skipIf?: boolean;
};