Interactive graph visualization for MTHDS method pipelines. Renders execution graphs from GraphSpec data — the canonical format produced by Pipelex when tracing pipeline execution.
Core graph logic (builders, layout, analysis) is pure TypeScript with no React dependency. The React layer provides a drop-in GraphViewer component powered by ReactFlow.
npm install @pipelex/mthds-uiReleased versions are listed on the npm page and the GitHub releases page.
If you render the React components, one setup step follows the install: the form controls that RunPanel and the graph's detail panel render need a stylesheet that your app loads itself. See Styling the form controls.
| Dependency | Required | Used by |
|---|---|---|
react, react-dom |
no | React layer (graph/react, form/react) |
shiki |
no | Syntax highlighting (shiki) |
| Dependency | License | Used by |
|---|---|---|
elkjs |
EPL-2.0 | Graph layout engine |
@xyflow/react |
MIT | Graph rendering (graph/react) |
@pipelex/mthds-form |
MIT | Run form panel + result view (form/react, graph/react) |
elkjs (Eclipse Layout Kernel) is licensed under the Eclipse Public License 2.0. See NOTICE for details.
import { GraphViewer } from "@pipelex/mthds-ui/graph/react";
function MethodGraph({ graphspec }) {
return <GraphViewer graphspec={graphspec} />;
}That's it. GraphViewer handles layout, styling, CSS variables, and all ReactFlow internals. All props except graphspec are optional with sensible defaults. The one exception to "styling" is the detail panel's data view, which shows a result through the form kernel's controls; those are styled by your app, as Styling the form controls describes.
ReactFlow accesses browser globals at module-evaluation time, so it cannot be server-side rendered. Use next/dynamic with ssr: false:
"use client";
import dynamic from "next/dynamic";
import type { GraphViewerProps } from "@pipelex/mthds-ui/graph/react";
import React from "react";
const GraphViewer = dynamic(
() =>
import("@pipelex/mthds-ui/graph/react").then((mod) => ({
default: mod.GraphViewer,
})),
{ ssr: false },
) as React.ComponentType<GraphViewerProps>;
export function MyGraph({ graphspec }) {
return <GraphViewer graphspec={graphspec} />;
}| Prop | Type | Default | Description |
|---|---|---|---|
graphspec |
GraphSpec | null |
— | Graph data (nodes + edges) |
config |
GraphConfig |
DEFAULT_GRAPH_CONFIG |
Layout and visual configuration |
direction |
GraphDirection |
"LR" |
Layout direction: TB, LR, RL, BT |
showControllers |
boolean |
false |
Show controller group outlines |
onNavigateToPipe |
(pipeCode: string, status?: PipeStatus) => void |
— | Callback when a pipe node is clicked |
onReactFlowInit |
(instance: AppRFInstance) => void |
— | Access the underlying ReactFlow instance |
The graph container uses position: absolute; inset: 0 to fill its parent. Make sure the parent element has position: relative and a defined height (e.g. h-full, flex-1, or an explicit height).
GraphSpec is the data format that describes a pipeline execution graph. It's generated by Pipelex when running or validating MTHDS method bundles.
# From a .mthds bundle file
pipelex-agent validate bundle my-method.mthds --view
# The output JSON contains a graphspec fieldOr programmatically via the Pipelex Python SDK:
from pipelex import Pipelex
px = Pipelex()
result = px.validate_bundle("my-method.mthds", view=True)
graphspec = result.graphspec # dict ready for JSON serializationFor authored-structure previews that should not run Pipelex, use the pure TypeScript static builder:
import { buildStaticGraphSpecFromToml } from "@pipelex/mthds-ui/static-graph";
const { spec, diagnostics } = buildStaticGraphSpecFromToml(tomlText);The returned spec has meta: { format: "mthds", mode: "static" } and can be
passed directly to GraphViewer. Static graphs hide runtime status/timing
chrome and are best-effort: diagnostics report incomplete or unresolved authored
content without preventing rendering.
See docs/static-graph.md for the full contract.
interface GraphSpec {
nodes: GraphSpecNode[];
edges: GraphSpecEdge[];
meta: {
format: "mthds";
mode?: "dry" | "live" | "static";
};
}
interface GraphSpecNode {
id: string;
pipe_code?: string; // e.g. "analyze_match"
pipe_type?: string; // e.g. "PipeLLM", "PipeSequence", "PipeExtract"
status?: string; // "succeeded", "failed", "running", "scheduled", "skipped"
io?: {
inputs?: IOItem[];
outputs?: IOItem[];
};
}
interface GraphSpecEdge {
id?: string;
source: string; // Source node ID
target: string; // Target node ID
kind: GraphSpecEdgeKind; // "data", "contains", "batch_item", etc.
label?: string;
}Nodes represent pipes (operations) and stuffs (data artifacts) in the pipeline:
- Pipe nodes — operations like
PipeLLM,PipeSequence,PipeExtract,PipeSearch - Stuff nodes — data flowing between pipes, typed by concepts (e.g.
CandidateProfile,Document)
| Kind | Description |
|---|---|
data |
Data flow — stuff produced by one pipe, consumed by another |
contains |
Containment — a controller pipe wraps child pipes |
batch_item |
Batch processing — items fanned out from a collection |
batch_aggregate |
Batch aggregation — items collected back |
parallel_combine |
Parallel results combined |
{
"nodes": [
{
"id": "extract_text",
"pipe_code": "extract_text",
"pipe_type": "PipeExtract",
"io": {
"inputs": [{ "digest": "input_doc", "name": "document", "concept": "Document" }],
"outputs": [{ "digest": "pages", "name": "pages", "concept": "TextPages" }]
}
},
{
"id": "summarize",
"pipe_code": "summarize",
"pipe_type": "PipeLLM",
"io": {
"inputs": [{ "digest": "pages", "name": "pages", "concept": "TextPages" }],
"outputs": [{ "digest": "summary", "name": "summary", "concept": "Summary" }]
}
}
],
"edges": []
}Full specification: docs.pipelex.com — Execution Graph Tracing
Controls layout and visual behavior. All fields are optional — DEFAULT_GRAPH_CONFIG provides sensible defaults.
import { DEFAULT_GRAPH_CONFIG } from "@pipelex/mthds-ui";
// Override specific settings
const myConfig = {
...DEFAULT_GRAPH_CONFIG,
direction: "LR",
nodesep: 80,
ranksep: 50,
};| Field | Type | Default | Description |
|---|---|---|---|
direction |
GraphDirection |
"LR" |
Layout direction |
showControllers |
boolean |
false |
Show controller group boxes |
nodesep |
number |
50 |
Horizontal spacing between nodes |
ranksep |
number |
100 |
Vertical spacing between ranks |
edgeType |
EdgeType |
"bezier" |
Edge curve style |
initialZoom |
number | null |
null |
Override fit-view zoom (null = auto) |
panToTop |
boolean |
true |
Pan viewport to top after layout |
paletteColors |
Record<string, string> |
(see below) | CSS variable overrides for theming |
GraphViewer applies CSS custom properties from paletteColors on mount. Override colors by passing a custom config:
<GraphViewer
graphspec={graphspec}
config={{
...DEFAULT_GRAPH_CONFIG,
paletteColors: {
...DEFAULT_GRAPH_CONFIG.paletteColors,
"--color-pipe": "#e06c75",
"--color-stuff": "#61afef",
"--color-bg": "#282c34",
},
}}
/>Default palette colors include:
| Variable | Purpose |
|---|---|
--color-pipe |
Pipe node border/accent |
--color-pipe-bg |
Pipe node background |
--color-stuff |
Stuff node border/accent |
--color-stuff-bg |
Stuff node background |
--color-edge |
Edge line color |
--color-batch-item |
Batch item edge color |
--color-batch-aggregate |
Batch aggregate edge color |
--color-bg |
Graph background |
--color-bg-dots |
Background dot pattern |
--font-sans |
Node font family |
--font-mono |
Code/controller font |
See graphConfig.ts for the full default palette.
| Import path | Content |
|---|---|
@pipelex/mthds-ui |
Pure-TS graph logic — types, builders, layout, controllers, config |
@pipelex/mthds-ui/graph/react |
React components — GraphViewer, label helpers, type converters |
@pipelex/mthds-ui/form |
The form kernel's React-free surface, re-exported |
@pipelex/mthds-ui/form/react |
RunPanel — a pipe's input form — and the kernel's controls |
@pipelex/mthds-ui/shiki |
MTHDS syntax highlighting with shiki |
@pipelex/mthds-ui/tailwind.css |
Where the kernel's classes are, for a Tailwind 4 host |
@pipelex/mthds-ui/form-kernel.css |
The kernel's prebuilt stylesheet, for a host without Tailwind |
Use the graph logic without React — build nodes/edges, run layout, and feed the result to your own renderer:
import {
buildGraph,
getLayoutedElements,
applyControllers,
DEFAULT_GRAPH_CONFIG,
} from "@pipelex/mthds-ui";
// Build graph data from a GraphSpec
const { graphData, analysis } = buildGraph(graphspec, "bezier");
// Apply ELK layout
const { nodes, edges } = getLayoutedElements(graphData.nodes, graphData.edges, "TB");
// Optionally wrap nodes in controller groups
const final = applyControllers(nodes, edges, graphspec, analysis, true);RunPanel renders a pipe's input form from its IO contract: the fields, the readiness verdict on the Run button, and the wire-ready payload a run receives.
The form kernel ships as a dependency of this package, so there is nothing extra to install. Reach it through @pipelex/mthds-ui/form rather than importing it directly — a second declaration puts a second copy in your tree, and the kernel ships React contexts, so a provider you mount above the panel would stop resolving inside it.
import { getPipeIOContract } from "@pipelex/mthds-ui/form";
import { RunPanel } from "@pipelex/mthds-ui/form/react";
import "@pipelex/mthds-ui/form/react/RunPanel.css";
// Note the argument order — the kernel's README currently shows it wrong.
const contract = getPipeIOContract(pipeIoContracts, domain, pipeCode);
<RunPanel
contract={contract}
values={values}
onValuesChange={setValues}
onRun={(apiInputs) => execute(pipeCode, apiInputs)}
title={pipeCode}
theme="dark"
/>;onRun fires only once the kernel's run gate passes. This library renders and never executes: no API client, no upload, no storage resolution — the host injects all three.
The controls inside RunPanel, and inside the graph's detail panel, are the form kernel's, styled with Tailwind classes over the shadcn tokens. No entry of this package imports a stylesheet for them: your app loads one, and which one depends on whether it runs Tailwind. Load one of the two, never both.
A host with Tailwind 4 follows the kernel's documented setup, A host that runs Tailwind, and imports this package's tailwind.css in place of that setup's @source line:
@import "tailwindcss";
@import "tw-animate-css";
@import "@pipelex/mthds-ui/tailwind.css";
@custom-variant dark (&:is(.dark *));
/* …and the @theme inline mapping of the shadcn tokens the kernel's setup lists. */tailwind.css points Tailwind at the copy of the kernel this package depends on, wherever pnpm or npm put it beside or under this package, so you write no path into node_modules and do not declare @pipelex/mthds-form yourself. One layout escapes it: when your tree holds two versions of this package, npm can nest one below a kernel hoisted further up, and the controls then render unstyled without a warning; docs/run-form-panel.md gives the one-line remedy. The rest of the kernel's setup stays yours: tw-animate-css animates the select popover and the tooltip, without the token mapping a class such as bg-background compiles to nothing, and the @custom-variant line, which a shadcn/ui codebase already has, keys dark: to the .dark class the kernel follows rather than to the operating system's preference.
A host without Tailwind imports the prebuilt stylesheet once, from its entry:
import "@pipelex/mthds-ui/form-kernel.css";It carries Tailwind's preflight, which resets the browser's default styles across your whole page (heading sizes, list markers, margins), and a cascade layer that lets every rule you write yourself win a tie against it.
Tailwind 3 hosts and Tailwind 4 hosts that configure a prefix are not supported: their builds cannot generate the kernel's unprefixed Tailwind 4 classes, and the prebuilt sheet cannot sit in their cascade, because its preflight must rank below their base styles while its utilities must rank above them.
Either way, the shadcn token values are yours to supply, and each must be a complete colour — hsl(240 10% 3.9%), #0a0711, oklch(…) — never a bare HSL triplet. Since kernel 0.8.0 the sheet emits background-color: var(--background) rather than hsl(var(--background)), so a triplet computes to something that is not a colour and the browser discards the declaration instead of overriding with it: green build, token inspectable, style silently absent.
Full contract, why this package stopped loading the kernel's stylesheet itself, the .dark bridge and the mthds-run-panel token hook: docs/run-form-panel.md.
Syntax-highlight MTHDS code with the bundled grammar and themes:
import { highlightMthds, getAvailableThemes } from "@pipelex/mthds-ui/shiki";
const html = await highlightMthds(mthdsSource, "pipelex-dark");npm install
make check # lint + format-check + typecheck
make test # unit tests (vitest)
make build # build to dist/MIT — see LICENSE.
This project depends on elkjs which is licensed under EPL-2.0. See NOTICE for third-party license details.