npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

journey-dsl

v1.2.0

Published

A TypeScript toolkit for defining, compiling, verifying, and running interactive user journeys as state machines.

Readme

journey-dsl

A TypeScript toolkit for defining, compiling, verifying, and running interactive user journeys as state machines.

Author flows in a readable .journey DSL, compile them to typed TypeScript definitions with codegen type maps, verify graph correctness, and run them with a built-in runtime engine. Or go in reverse: extract .journey files from an existing React codebase.

Install

npm install journey-dsl

Quick Start

# Scaffold a new project
npx journey init

# Create a journey
npx journey new daily-checkin --personas user,admin

# Edit journeys/dsl/authored/daily-checkin.journey, then:
npx journey compile

# Verify everything
npx journey check

New to journey-dsl? The Build an App guide walks you through building a complete onboarding flow from scratch — from writing the .journey file through component wiring, verification, and runtime setup. It covers the end-to-end concepts that this README's API reference assumes you already know.

The .journey DSL

Journeys are authored as indentation-based .journey files that describe state machines with nodes (screens, decisions, exits) and edges (transitions between them).

journey daily-checkin
  description "Daily mood check-in with optional journaling"
  persona survivor guardian

  effect save_checkin
    api POST /api/checkins
    req { mood: string!, journalText: string? }
    res { checkinId: string!, streakDays: number! }
    err 400 invalid_mood

  entry mood-screen "How Are You Feeling?"
    ui MoodScreen
    produces { mood: string! }

  decision journal-check "Should Journal?"

  view journal-screen "Write a Journal Entry"
    ui JournalScreen
    requires { mood: string! }
    produces { journalText: string! }

  exit success "Complete"
  abandon skip "Skip for Now"

  mood-screen -> skip                close
  mood-screen -> journal-check       submit
  journal-check -> journal-screen    @system route    guard "mood !== 'great'" "Bad days"
  journal-check -> success           @system route
  journal-screen -> success          save_checkin

Node Types

| Type | Screen? | Purpose | | ------------ | ------- | --------------------------------------------- | | entry | Yes | Journey start point (exactly one per journey) | | view | Yes | Interactive screen | | decision | No | Routing logic via guards | | action | No | Non-interactive processing | | exit | No | Successful completion (absorbing) | | abandon | No | User cancellation (absorbing) | | error | No | Error state (absorbing) | | subjourney | No | Reference to another journey |

Node Properties

entry welcome "Welcome Screen"
  ui WelcomeScreen               # Component binding (entry/view only)
  requires { userId: string! }   # Data this node needs
  produces { userName: string! } # Data this node emits

Field types: string, number, boolean, object, array. Append ! for required (default), ? for optional.

Effects

Declare API contracts that edges can reference:

effect create_user
  api POST /api/users
  req { name: string!, email: string! }
  res { userId: string! }
  err 400 validation_error
  err 409 duplicate_email

Edges

source -> target    trigger [+ effect]...    [guard "condition" "description"]

Trigger types:

  • User action (button click): next, submit, close
  • System event (async): @system route, @system data_loaded
  • Navigation (route change): @nav /path, @nav /users/:userId

Navigation routes (parameterized):

  • @nav /settings — static route
  • @nav /users/:userId — parameterized route (:userId extracted from data at runtime)

Guards add conditions to edges. Prefer declarative guards for type safety:

# Boolean check
gate -> profile    next    guard hasProfile "Has profile"
gate -> setup      next    guard not hasProfile "No profile"

# Field equality
gate -> crisis     route   guard mood is "struggling" "Mood check"
gate -> normal     route   guard mood is_not "struggling" "Normal flow"

# Enum membership
gate -> hard-day   route   guard mood in [struggling, mixed] "Hard day"

# Role/permission check
gate -> admin      route   guard role:admin "Admin only"

# Expression (backward compatible)
gate -> special    route   guard "score > 80" "High score"

Effects are chained with +:

form -> result    submit + save_checkin + send_notification

Configuration

Create journey.config.ts at your project root:

import { defineConfig } from "journey-dsl";

export default defineConfig({
  journeyDirs: ["journeys/dsl/authored"],
  definitionsDir: "journeys/definitions",
  indexFile: "journeys/definitions/index.ts",
  stories: {
    graph: "stories/organisms/JourneyGraph.stories.js",
    walkthrough: "stories/organisms/JourneyWalkthrough.stories.js",
    visualizer: "stories/visualizer/JourneyVisualizer.stories.js",
  },
  specs: {
    openapi: ["specs/api.yaml"],
    graphql: ["specs/schema.graphql"],
  },
  rootJourney: "app-lifecycle",
});

All paths are relative to the config file. Every field has sensible defaults matching the layout above.

CLI

| Command | Description | | --------------------------------------- | ------------------------------------------------------------------ | | journey init | Scaffold folder structure, config, and empty barrel | | journey new <name> [--personas p1,p2] | Create .journey template and update barrel | | journey compile [--force] [--dry-run] | Compile .journey.generated.ts + JourneyNodes type map, update barrel + stories | | journey validate [--all] [files...] | Static AST and cross-file validation without writing files | | journey verify [--no-spec] [--strict] [--quiet] [--format=json] | Full graph verification | | journey gen:stories | Regenerate Storybook story files from compiled definitions | | journey check [--no-spec] [--strict] [--quiet] [--format=json] | All-in-one: validate + compile + verify | | journey explore [file.journey] | Launch interactive visualizer dev server (port 4777) | | journey watch [--verify] | Watch .journey files, validate on change | | journey diff <file.journey> | Semantic diff: source vs compiled output | | journey doctor | Health check for project setup | | journey install-hooks | Install git pre-commit hook for validation | | journey extract <dir> [--entry file] | Extract .journey files from an existing React codebase | | journey paths [name] [--json] | Enumerate all distinct paths through a journey | | journey explain <name> [--json] | Auto-generated plain-English journey description | | journey query <expression> [--json] | Structured query over journey graphs | | journey coverage [name] [--json] [--lcov] [--out file] | Static path coverage report | | journey report [--format md\|json] [--out file] | Generate journey catalog report | | journey openapi [--out file] [--title name] | Generate OpenAPI spec from all journey effects | | journey simulate <name> [--auto] [--visual] [--port N] | Walk through a journey (CLI REPL, auto mode, or visual dev server) | | journey autopilot <name> [flags] | Playwright-driven automated path traversal with smart form filling | | journey prove [--strict] [--format=json] [--no-spec] | Formal verification proof of all journey contracts | | journey generate <name> [flags] | Generate components, registry, effects, tests, API routes, typed client |

Extraction (Brownfield Projects)

Already have a React app? Extract .journey files from your existing codebase as a starting point:

# Dry run — see the generated .journey source and Mermaid graph
journey extract ./my-react-app --entry src/App.tsx --dry-run

# Write to a directory
journey extract ./my-react-app --entry src/App.tsx --out ./journeys/extracted

# Scope to a specific route subtree
journey extract ./my-react-app --entry src/App.tsx --scope /checkout

# Choose a parser backend (auto picks the fastest available)
journey extract ./my-react-app --entry src/App.tsx --parser oxc

The extractor:

  1. Builds a module graph starting from your entry point, following relative imports (skipping packages and assets)
  2. Detects routes from createBrowserRouter([...]) or <Routes> JSX (React Router v6)
  3. Analyzes navigation — finds navigate() calls, <Link> elements, and <Navigate> redirects across all route components
  4. Classifies nodes — entry, view, exit, error, and abandon based on route paths and patterns
  5. Assembles a journey graph and decompiles it to a .journey file you can refine

Supports two parser backends: oxc-parser (faster, preferred) and @swc/core. Install either as a peer dependency.

The extracted .journey file is a starting point — review it, add guards, effects, data schemas, and ui bindings to make it production-ready.

CI Integration

Use --strict, --quiet, and --format=json flags for CI pipelines:

# Fail on warnings (not just errors)
journey check --strict

# Formal proof — single read-only command that proves all journeys are data-complete
journey prove --strict

# Machine-readable proof certificate
journey prove --format=json

# Machine-readable output for CI parsers
journey verify --format=json

# Suppress info-level diagnostics
journey check --strict --quiet

journey prove

A single read-only command that formally verifies all journey contracts. Runs a 5-step pipeline:

  1. Parse — find and parse all .journey files
  2. Validate — AST + cross-file validation
  3. Compile — compile to graph definitions
  4. Verify — graph checks (reachability, data flow, API contracts, etc.)
  5. Component Contracts — verify components satisfy journey node contracts

Outputs PROVEN or NOT PROVEN with a summary. With --format=json, produces a machine-readable proof certificate suitable for CI gates.

Install a git pre-commit hook that validates staged .journey files before each commit:

journey install-hooks

The hook only validates files in the current commit, so it stays fast.

Testing Strategy

journey-dsl provides a six-layer testing pyramid — from static analysis (~2 seconds, no app) through to full E2E browser automation against your running application.

# Layer 1: Static — structure, data flow, components, API spec
journey check --strict

# Layer 2: Simulation — walk all paths with mock data
journey simulate onboarding --auto

# Layer 3: Unit tests — generated Vitest file with one test per path
journey generate onboarding --tests
vitest run tests/journeys/

# Layer 5: Autopilot — Playwright against simulation server
journey simulate onboarding --visual --no-open &
journey autopilot onboarding --screenshots --emit-tests tests/

Layers 1-3 are headless, run in under 10 seconds, and catch ~90% of issues. Layers 5-6 add browser-level confidence with screenshots and emitted Playwright test files.

See the full Testing Guide for setup, examples, what each layer catches, and a recommended CI pipeline.

Explorer

Launch a standalone interactive visualizer without Storybook:

# Explore all journeys in configured directories
journey explore

# Focus on a specific file
journey explore journeys/dsl/authored/daily-checkin.journey

The explorer starts a Vite dev server on port 4777 with:

  • Journey picker sidebar — searchable list of all discovered journeys with node/edge counts and personas
  • Drag-and-drop — drop .journey files onto the explorer to compile and visualize them on the fly
  • Single-file mode — pass a file argument to open directly to that journey
  • No React install needed — React is resolved from the package internals, so your project doesn't need it as a dependency

Simulation

# CLI REPL — step through manually
journey simulate daily-checkin

# Auto-advance through all paths
journey simulate daily-checkin --auto

# Visual dev server with live state inspection
journey simulate daily-checkin --visual --port 4778

The visual simulation server provides:

  • REST APIGET /state, POST /dispatch, POST /merge-data, POST /auto-fill, POST /system-event, POST /reset
  • SSE streamGET /events for real-time state sync
  • Inline harness — HTML UI with node card, action buttons, data inspector, and auto-fill

Autopilot

Automated browser-driven path traversal using Playwright. Runs every enumerated path through a real browser with smart form filling.

# Basic run against the visual simulation server
journey autopilot daily-checkin

# With screenshots and test file emission
journey autopilot daily-checkin --screenshots --emit-tests tests/daily-checkin.spec.ts

# Headed mode for visual observation
journey autopilot daily-checkin --headed --delay 1000

Features:

  • Guard-aware data generation — picks field values that steer toward unvisited guard branches
  • Multi-strategy selectors — tries [data-journey-field], input[name], button:has-text(), etc.
  • Screenshot capture--screenshots saves a PNG at each step
  • Test file emission--emit-tests generates a complete Playwright test file with exact selectors and values
  • CI integration — exits non-zero on failure; pair with --screenshots for evidence

Requires @playwright/test as a peer dependency: npm install -D @playwright/test && npx playwright install

Code Generation

# Generate everything for a journey
journey generate daily-checkin --full

# Just component stubs
journey generate daily-checkin --components

# Backend API route stubs from effects
journey generate daily-checkin --api --framework=hono

# Typed fetch client
journey generate daily-checkin --client

Flags: --components, --registry, --effects, --tests, --api, --client, --full, --force, --framework=react|express|hono|nextjs

Configure defaults in journey.config.ts:

export default defineConfig({
  // ...
  generate: {
    library: 'shadcn',        // component library preset
    importStyle: 'named',     // 'named' | 'default'
    apiFramework: 'hono',     // 'express' | 'hono' | 'nextjs'
    componentDir: 'src/components',
    apiDir: 'src/api',
  },
});

Programmatic API

Parse & Compile

import { parse, compile, decompile } from "journey-dsl";

const ast = parse(sourceText);
const definition = compile(ast);
const roundTripped = decompile(definition);

Validation

import { validateAST, validateCrossFile } from "journey-dsl";

const diagnostics = validateAST(ast, { filePath: "my-flow.journey" });
const crossFileDiags = validateCrossFile(astMap, {
  rootJourney: "app-lifecycle",
});

Graph Construction

import {
  JourneyGraph,
  entry,
  view,
  exit,
  edge,
  resetEdgeCounter,
} from "journey-dsl";

resetEdgeCounter();
const graph = new JourneyGraph();

graph.addNode(entry({ id: "start", label: "Start", ui: "StartScreen" }));
graph.addNode(exit({ id: "done", label: "Done" }));
graph.addEdge(
  edge({
    source: "start",
    target: "done",
    trigger: { type: "user_action", action: "next" },
  }),
);

Verification

import { verify } from "journey-dsl";

const result = verify(graph, {
  knownJourneyNames: new Set(["daily-checkin"]),
  externalRegistry: apiRegistry,
});

// result.diagnostics: VerificationDiagnostic[]
// result.hasErrors: boolean

Individual checks are also exported: checkReachability, checkDeadEnds, checkCycles, checkAbsorbing, checkGuards, checkDataFlow, checkApiContracts, checkFormSchemas, checkSubjourneys, checkCircularSubjourneys, checkComplexity, checkComponentContracts, checkRegistryCompleteness.

Component Contract Verification

Statically analyze JSX components to verify they satisfy journey node contracts — without running the app:

import { extractComponentBindings, checkComponentContracts } from "journey-dsl";

const bindings = extractComponentBindings(["src/components"], parser);
// bindings: Map<string, ComponentBindingInfo>
// Each entry has: producesFields, actions, readsFields, props

const diagnostics = checkComponentContracts(graph, bindings);
// Checks: produces coverage, action coverage, requires reads

The extractor finds <input name="...">, mergeData({...}), dispatch('...'), data-action, data-journey-field, and data.x/props.x access patterns in component source.

Data-Flow Verification

The checkDataFlow check goes beyond node requirements — it also verifies:

  • Effect request fields — every field in an effect's requestSchema is available at the edge's source node
  • Effect response propagation — fields from an effect's responseSchema become available at the target node (e.g., an API call that returns { token } satisfies a downstream requires { token })
  • Guard field references — declarative guards referencing a field (e.g., guard mood is "struggling") have that field available at the source node
  • Route param availability — parameterized route params (e.g., :userId in @nav /users/:userId) are available as data fields at the source node

Codegen Type Map

The compiler generates a JourneyNodes interface for each journey, enabling compile-time verification that components satisfy the graph's data contract:

// AUTO-GENERATED in daily-checkin.generated.ts
export interface DailyCheckinNodes {
  'mood-screen': {
    requires: {};
    produces: { mood: string };
    actions: ['submit', 'close'];
    ui: 'MoodScreen';
  };
  'journal-screen': {
    requires: { mood: string };
    produces: { journalText: string };
    actions: ['save_checkin'];
    ui: 'JournalScreen';
  };
}

Use satisfies to verify your registry at compile time:

import type { DailyCheckinNodes } from './daily-checkin.generated';

const registry = {
  'mood-screen': (props: DailyCheckinNodes['mood-screen']['requires']) => renderMoodScreen(props),
} satisfies Partial<Record<keyof DailyCheckinNodes, (props: any) => any>>;

Runtime

journey-dsl provides two runtime APIs:

  • createStateMachine — render-agnostic state machine. Your app provides a component registry keyed by ui binding names. The state machine resolves which component to render — no switch statements.
  • createRuntime — DOM-based wrapper that auto-renders via a render callback and listens for journey:action/journey:data CustomEvents.

createStateMachine (render-agnostic)

import { createStateMachine } from "journey-dsl";

const sm = createStateMachine({
  journey: definition,

  journeyMap: new Map([
    ["daily-checkin", dailyCheckin()],
    ["safety-plan", safetyPlan()],
  ]),

  effectHandlers: {
    api_call: async (effect, data) => {
      const res = await fetch(effect.payload.endpoint, {
        method: effect.payload.method,
      });
      return res.json();
    },
  },

  onTransition: (state, from, to, edge) => {
    console.log(`${from.id} -> ${to.id}`);
  },

  onUnhandledAction: (nodeId, action) => {
    console.warn(`No edge for action "${action}" at node "${nodeId}"`);
  },
});

// Core API
await sm.dispatch("next");         // Trigger a user_action edge
sm.sendSystemEvent("data_loaded"); // Trigger a system_event edge
sm.mergeData({ name: "Alice" });   // Update journey data store
sm.getState();                     // { currentNodeId, data, history }
sm.getCurrentNode();               // Current JourneyNode (includes .ui binding)
sm.getGraph();                     // Active JourneyGraph (child graph when inside subjourney)
sm.getContextDepth();              // 0 = root, 1+ = inside nested subjourney
sm.getDiagnostics();               // RuntimeDiagnostic[]

// Subscribe for reactive updates (React, Svelte, etc.)
const unsubscribe = sm.subscribe((state) => {
  // Re-render — the current node's .ui tells you which component
});

React integration — registry pattern (no switch):

The journey file declares ui ComponentName on each node. Your app builds a registry mapping those names to components. The state machine resolves the active node — you just look up node.ui in the registry:

import { WelcomeScreen, NameInput, AdminSetup, MemberSetup } from "./components";

// Registry: one entry per `ui` binding in your journey file
const registry = new Map([
  ["WelcomeScreen", WelcomeScreen],
  ["NameInput", NameInput],
  ["AdminSetup", AdminSetup],
  ["MemberSetup", MemberSetup],
]);

function JourneyRenderer({ sm }: { sm: StateMachineHandle }) {
  const [state, setState] = useState(sm.getState());
  useEffect(() => sm.subscribe(setState), [sm]);

  const node = sm.getCurrentNode();
  const Component = registry.get(node.ui!);
  if (!Component) return null;

  // Every component gets the same props — data + actions
  return <Component data={state.data} dispatch={sm.dispatch} mergeData={sm.mergeData} />;
}

No routing logic, no switch statement. The journey file is the single source of truth for which component renders at each step.

createRuntime (DOM-based)

Wraps createStateMachine with DOM rendering and event listeners. You provide a render callback — same registry lookup pattern:

import { createRuntime } from "journey-dsl";
import { WelcomeScreen, NameInput } from "./components";

const registry = new Map([
  ["WelcomeScreen", WelcomeScreen],
  ["NameInput", NameInput],
]);

const runtime = createRuntime({
  journey: definition,
  container: document.getElementById("app"),

  render: (node, data, el) => {
    el.innerHTML = "";
    const Component = registry.get(node.ui!);
    if (Component) el.appendChild(Component(data));
  },

  effectHandlers: {
    api_call: async (effect, data) => {
      const res = await fetch(effect.payload.endpoint, {
        method: effect.payload.method,
      });
      return res.json();
    },
  },
});

// Components dispatch journey:action and journey:data CustomEvents
// The DOM runtime handles them automatically
runtime.dispatch("next"); // Or trigger programmatically
runtime.getState(); // { currentNodeId, data, history }
runtime.getContextDepth(); // 0 = root journey, 1+ = inside subjourney
runtime.getDiagnostics(); // RuntimeDiagnostic[] (includes post-render-dom, dead-render)

The DOM runtime automatically:

  • Listens for journey:action and journey:data CustomEvents on the container
  • Renders the active node's component from the registry on each transition
  • Shows/clears validation errors on [data-journey-field] elements
  • Runs post-render DOM validation against component manifests (checks [data-action], button text, [name], [data-journey-field] attributes)
  • Detects dead renders (empty container content when manifest declares interactive elements)

Subjourney resolution

Both runtimes support nested subjourneys via a context stack. When a transition lands on a subjourney node:

  1. The node's journeyRef is looked up in journeyMap
  2. The parent context (graph, node position, exit mapping) is pushed onto a stack
  3. The child journey's entry node becomes the active node
  4. When the child journey reaches an exit or abandon node, the runtime pops the stack, maps the exit node ID through exitMapping to a system event, and dispatches that event in the parent — continuing the parent journey

Data is shared across parent and child (single state.data store). Nesting depth is unlimited. If journeyMap is not provided or the journeyRef can't be resolved, a subjourney-error diagnostic is emitted.

Form Validation Adapters

Wrap Zod or Yup schemas for use as node formSchema:

import { zodSchema, yupSchema } from "journey-dsl";

const schema = zodSchema(
  z.object({
    email: z.string().email(),
    age: z.number().min(18),
  }),
);

const result = schema({ email: "[email protected]", age: 25 });
// { valid: true, fieldErrors: {} }

API Spec Compliance

Check that journey effects match external OpenAPI/GraphQL specs:

import {
  loadOpenApiSpec,
  buildRegistry,
  checkApiSpecCompliance,
} from "journey-dsl";

const registry = await buildRegistry({
  openapi: ["specs/api.yaml"],
  graphql: ["specs/schema.graphql"],
});

const diagnostics = checkApiSpecCompliance(graph, registry);

Formatting

import {
  formatDiagnostic,
  formatJourneyResult,
  formatSummary,
} from "journey-dsl";

Analysis & Tooling Libraries

import {
  // Path enumeration
  enumeratePaths,
  formatPaths,

  // Coverage tracking
  createCoverageTracker,

  // Analytics middleware
  withAnalytics,
  consoleSink,
  fileSink,
  callbackSink,

  // Semantic diff
  semanticDiff,
  formatDiff,

  // Smart data generation
  createSmartDataGenerator,
  generateNodeData,
  generateEffectResponse,
  generateFieldValue,
} from 'journey-dsl';

Coverage tracking:

const tracker = createCoverageTracker(graph);
tracker.observe(sm); // auto-tracks transitions
// or manually: tracker.record(nodeId, edgeId);

const report = tracker.report();
// { nodes: { total, visited, percentage }, edges: {...}, paths: {...} }

const lcov = tracker.toLcov('daily-checkin'); // for CI dashboards

Analytics middleware:

const tracked = withAnalytics(sm, {
  sinks: [consoleSink(), fileSink('./analytics.jsonl'), callbackSink(sendToPostHog)],
  sampleRate: 0.5,
});
// Emits: node_enter, node_exit, journey_complete, journey_abandon, journey_error

Semantic diff:

const diff = semanticDiff(beforeAST, afterAST);
console.log(formatDiff(diff));
// + node journal-screen [view]
// ~ edge mood→journal: guard changed
// - effect old_effect

Vite Plugin

Import .journey files directly in your app with HMR support:

// vite.config.ts
import { journeyPlugin } from "journey-dsl/vite";

export default {
  plugins: [journeyPlugin()],
};
// In your app
import { journey } from "./daily-checkin.journey";
const definition = journey(); // JourneyDefinition

Verification Diagnostics

Every check produces diagnostics with a severity and category:

Severities: error | warning | info

Categories:

| Category | What it checks | | ---------------------- | -------------------------------------------- | | reachability | All nodes reachable from entry | | dead-ends | All nodes can reach an exit/abandon | | cycles | Loops have exit edges | | absorbing | Exit nodes exist | | guards | Decision guards are exhaustive (boolean, field, enum pairs) | | data-flow | Required data available at each node, effect req/res fields, route params | | api-contracts | Effect schemas are consistent | | api-spec-compliance | Effects match external OpenAPI/GraphQL specs | | subjourney-refs | Referenced sub-journeys exist | | circular-subjourneys | Circular subjourney reference cycles | | complexity | High branching (>5 edges) or large journeys (>30 nodes) | | unused-journeys | Journeys not reachable from rootJourney | | ui-bindings | Component existence + prop matching from componentDirs | | component-contracts | Components satisfy journey produces/actions/requires contracts | | ui-bindings (registry) | All ui bindings present in the runtime component registry |

Visualizer

An interactive React-based journey visualizer with graph layout, simulation, node inspection, and diagnostics. Available as a separate entry point so the core package stays React-free.

# Install peer dependencies (only needed for the visualizer)
npm install react react-dom @xyflow/react dagre

Mount in any container

import { mountVisualizer } from "journey-dsl/visualizer";

const cleanup = mountVisualizer(
  document.getElementById("viz"),
  journeyDefinition,
  storyRegistry,    // optional — enables component preview mode
  journeyMap,       // optional — Map<name, JourneyDefinition> for subjourney expansion
);

// Later: cleanup() to unmount

Use individual components

All visualizer internals are exported for custom layouts:

import {
  // Layout
  graphToReactFlow,

  // React components
  JourneyNodeComponent,
  JourneyEdgeComponent,
  DiagnosticsPanel,
  NodeInspector,
  SimulationControls,
  SearchBar,
  ExportMenu,

  // Hooks
  useSimulation,
  useKeyboardShortcuts,

  // Analysis
  analyzeDataFlow,
} from "journey-dsl/visualizer";

import type {
  ViewMode,
  StoryRegistry,
  JourneyMap,
  SimulationState,
} from "journey-dsl/visualizer";

Storybook integration

The journey gen:stories command auto-generates Storybook stories that use the visualizer. Each journey gets its own fullscreen story with interactive graph navigation, simulation controls, and live diagnostics.

Features

  • Summary / Preview modes — toggle between compact node labels and live component previews
  • Simulation — step through journeys manually or auto-play, with accumulated data tracking and form validation status
  • Node inspector — click any node to see data requirements, productions, incoming/outgoing edges
  • Diagnostics panel — live verification results grouped by node or category, with severity filtering
  • Diagnostic badges — red/yellow badges on nodes with verification errors or warnings
  • Subjourney expansion — expand subjourney nodes inline to see the child journey's full graph
  • Dagre auto-layout — automatic left-to-right graph layout with happy path highlighting
  • Search — press / to search nodes by id, label, type, or ui binding
  • Keyboard shortcuts — Space (play/pause), Arrow Right (step), R (reset), E (zoom to error), F (fit), ? (help)
  • Export — Mermaid, SVG, JSON to clipboard; JSON file download
  • Coverage overlay — toggle to see visited vs unvisited nodes/edges with color-coded stats
  • Data flow analysis — trace field producers/consumers through the graph
  • Responsive layout — desktop (full sidebar), tablet (icon strip + overlay panels), mobile (bottom sheet + touch targets)

Peer Dependencies

All optional:

  • vite >= 5 — for the Vite plugin
  • @apidevtools/swagger-parser >= 12 — for OpenAPI spec loading
  • graphql >= 16 — for GraphQL schema loading
  • react >= 18 — for the visualizer
  • react-dom >= 18 — for the visualizer
  • @xyflow/react >= 12 — for the visualizer
  • dagre >= 0.8 — for the visualizer
  • @swc/core >= 1.15 — for journey extract (fallback parser, if oxc-parser doesn't support your platform)
  • @playwright/test >= 1.40 — for journey autopilot

License

Proprietary. See LICENSE for details.