Skip to content
PipelexPublic

About

React components and utilities for visualizing and interacting with MTHDS methods — graph viewer, layout engine, and building blocks for method-aware web applications.

Topics

Resources

Stars

1 star

Watchers

0 watching

Forks

Latest commit

 

History

348 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

@pipelex/mthds-ui

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.

Install

npm install @pipelex/mthds-ui

Released 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.

Peer dependencies

Dependency Required Used by
react, react-dom no React layer (graph/react, form/react, timeline/react)
shiki no Syntax highlighting (shiki)

Bundled dependencies

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)
mthds MIT The problem document's type on a failed node, types only

elkjs (Eclipse Layout Kernel) is licensed under the Eclipse Public License 2.0. See NOTICE for details.

Quick start (React)

import { GraphViewer } from "@pipelex/mthds-ui/graph/react";

function MethodGraph({ graphspec }) {
  return <GraphViewer graph={{ graphSpec: graphspec }} />;
}

That's it. GraphViewer handles layout, styling, CSS variables, and all ReactFlow internals. All props except graph 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.

Next.js (App Router)

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 graph={{ graphSpec: graphspec }} />;
}

Showing what a run produced

graph carries the spec together with the artifacts that describe its pipes, and the detail panel uses them to lay a data node's value out. They must all come from one source, because the panel looks each value's descriptor up by the pipe_ref of the pipe that produced it in this graph: a run's own results, or a method's validate report beside that method's dry or static graph. A run's results carry all four, and graphArtifactsFrom reads them in one call:

import { GraphViewer, graphArtifactsFrom } from "@pipelex/mthds-ui/graph/react";

// results: { graph_spec, pipe_io_contracts, output_form, input_form }
const graph = graphArtifactsFrom(results);
return <GraphViewer graph={graph} />;

Without pipeIoContracts and outputForm the panel shows each concept's structure table and no data tab. See docs/stuff-result-panel.md.

Showing why a run failed

A failed node carries the MTHDS Protocol's problem document in its error (the Problem type of mthds/errors). Its detail panel shows the problem's title, code and detail, the pipe it failed in with the path down to it, and its next step, reading only the standard's core members, so a problem from any conforming runtime renders. The graph rings the pipe the run failed in: the node whose own problem's location names its pipe, not the controllers that failed because of it. See docs/run-problem.md.

When a spec cannot be drawn

GraphViewer never throws a spec it cannot draw out of render. A spec validateGraphSpec refuses, from a runtime newer or older than this package for instance, is drawn as an alert in place of the graph, naming the location refused and what was found there, and so is a spec the build or its first layout fails on. Your page stays up, and the next spec you pass is drawn afresh. A layout that fails after the reader changes the direction, the folds or the style is drawn as an alert over the dimmed graph instead, with the toolbar kept so they can change the view back. See docs/error-state.md.

GraphViewer props

Prop Type Default Description
graph GraphArtifacts | null — The spec and its descriptors, one source
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
graphStyle GraphStyleId "detailed" The style the graph is drawn in: detailed or simple
styleMenu boolean | GraphStyleId[] — Offer a style menu in the toolbar (opt-in)
onGraphStyleChange (style: GraphStyleId) => void — Called when the style changes, so the host can keep the choice
interactive boolean true false draws a still picture sized to its content

The simple style draws the method as a flowchart in plain words: steps titled by their descriptions, only the method's inputs and final output as data, decisions with their outcomes, and loops as "for each". See docs/graph-styles.md.

Container sizing

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).

The still presentation (interactive={false}) is the exception: its container flows in its parent and takes the height the drawing needs, so the parent gives it a width and no height. See docs/still-presentation.md.

GraphSpec

GraphSpec is the data format that describes a pipeline execution graph. It's generated by Pipelex when running or validating MTHDS method bundles.

Generating a GraphSpec

# From a .mthds bundle file
pipelex-agent validate bundle my-method.mthds --view

# The output JSON contains a graphspec field

Or 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 serialization

Static GraphSpec from .mthds

For 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.

Structure

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[];
  };
  error?: Problem; // why a failed node failed: the problem document of "mthds/errors"
}

interface GraphSpecEdge {
  id?: string;
  source: string; // Source node ID
  target: string; // Target node ID
  kind: GraphSpecEdgeKind; // "data", "contains", "batch_item", etc.
  label?: string;
}

Node types

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)

Edge kinds

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

Example GraphSpec (minimal)

{
  "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

Configuration

GraphConfig

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
graphStyle GraphStyleId "detailed" The style the graph is drawn in
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

Theming with palette colors

GraphViewer applies CSS custom properties from paletteColors on mount. Override colors by passing a custom config:

<GraphViewer
  graph={{ 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.

Entry points

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/brand The kernel's brand entry: seed colours into readable colour roles
@pipelex/mthds-ui/form/react RunPanel — a pipe's input form — and the kernel's controls
@pipelex/mthds-ui/timeline A run's progress snapshot: types, path grammar, rows and time axis
@pipelex/mthds-ui/timeline/react RunTimeline — a run's progress as growing bars or a checklist
@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

Pure TypeScript usage

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);

Run form panel

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 kernel's two other host seams travel the same way: fileSource lets a file field take a file the host already stores, and exampleSource lets the form be filled from one of the method's saved examples, asking first before it replaces what the person entered, then calling onFill so a host knows the form was reset rather than edited. A panel given neither offers neither. See docs/run-form-panel.md.

Styling the form controls

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.

To brand the controls with a team's own colours, @pipelex/mthds-ui/form/brand derives colour roles from up to three colours a person picked, every text colour readable on the theme's page: a host writes action onto --primary, accentInk onto the optional --link (the accent as text) and onto --ring (every focus the controls draw), and accentFill onto the optional --selected (the pale fill of a selected pill, tag or step). A role the team left unset comes back absent, so the host writes no token for it and keeps its own, and a page that defines neither --link nor --selected falls back to --primary for both.

Full contract, why this package stopped loading the kernel's stylesheet itself, the .dark bridge, the mthds-run-panel token hook and the brand tokens: docs/run-form-panel.md.

Run timeline

RunTimeline draws a running run as nested rows, one per invocation of a pipe, each with a bar on a shared time axis that grows while the invocation runs. Its only input is the progress snapshot of the hosted progress contract: a batch is one collapsible row with its counts and a strip of its items' states, whose items come a page at a time (batchPageSize, ten by default) with a jump to the next failed or stuck one, a condition's branch carries the outcome it took, and a retried or stuck invocation is marked on its own row.

import { RunTimeline } from "@pipelex/mthds-ui/timeline/react";

// Hand it every snapshot you read: it keeps the fresher one by the contract's
// rule, so a late answer from an older read never moves it backwards.
<RunTimeline snapshot={snapshot} theme="dark" />;

timelineStyle="simple" draws the same run as a checklist of its steps in the method's own words, titled from the method's pipe registry the host passes as pipeRegistry, with each batch as one line of counts, for a reader who did not write the method; styleMenu lets the reader switch. See docs/timeline-styles.md.

The entry imports its own stylesheet, and the timeline themes itself with the same tokens as GraphViewer. The snapshot's types and the projection the component draws are in the React-free @pipelex/mthds-ui/timeline. Props, the freshness rule, what each row and bar shows, and the stories: docs/run-timeline.md.

Shiki integration

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");

Development

npm install
make check    # lint + format-check + typecheck
make test     # unit tests (vitest)
make build    # build to dist/

Storybook's anonymous usage telemetry is disabled in .storybook/main.ts (core.disableTelemetry), because no test run, locally or in CI, may report to a third party: without it, every make test would send an event to storybook.js.org through the Storybook test project, and so would every make storybook. Every CI workflow also sets DO_NOT_TRACK=1.

License

MIT — see LICENSE.

This project depends on elkjs which is licensed under EPL-2.0. See NOTICE for third-party license details.

About

React components and utilities for visualizing and interacting with MTHDS methods — graph viewer, layout engine, and building blocks for method-aware web applications.

Topics

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages