A visual, interactive toolkit for building and simulating Finite State Machines.
Design, connect, and explore DFAs, NFAs, and free-form state machines — all from your browser.
- Overview
- Features
- Architecture
- Tech Stack
- Getting Started
- Project Structure
- Engine API
- Contributing
- Roadmap
- License
FSM Engine is an open-source, web-based tool for creating, editing, and visualizing Finite State Machines. It pairs a powerful, framework-agnostic TypeScript engine (@fsm/engine) with a rich, canvas-driven Svelte 5 frontend — giving you both a programmatic API and a visual editor in a single monorepo.
Whether you're a student learning automata theory, an educator preparing a lecture, or an engineer prototyping state-driven logic, FSM Engine gets you from concept to diagram in seconds.
- Infinite canvas with smooth zoom & pan (scroll / pinch / trackpad)
- Drag-to-reposition states anywhere on the canvas
- Light & dark theme with toggleable grid overlay (light and dark theme switching will be available soon)
| Mode | Description |
|---|---|
| Add | Click the canvas to place a new state |
| Connect | Click two states to draw a directed transition (self-loops supported) |
| Remove | Single-click to delete any state or transition |
| Zoom | Zoom in / out with buttons or scroll wheel |
- Rename states and set custom fill colors via a right-click properties panel
- Mark any state as Start, Intermediate, or End
- Edit transition labels with inline controls
- New / Open / Save projects in a portable
.fsm(JSON) format - Export to PNG at 1×, 2×, or 3× resolution
- Project metadata (name, author, creation date) fully editable from the UI
- One-click Dagre-powered graph layout that smoothly animates nodes into an optimal arrangement
- Pure TypeScript, zero-DOM — use it in Node.js, Bun, Deno, or any JS runtime
- Supports Free-form, DFA, and NFA engine types (More coming soon)
- DFA mode enforces deterministic constraints and provides string validation with path tracing
- Efficient ID recycling via min-heap for states and transitions
- Full save / load serialization out of the box
FSM Engine is structured as a Bun workspace monorepo with a clear separation between the computational core and the visual layer:
┌─────────────────────────────────────────────────────────┐
│ Monorepo Root │
│ (bun workspaces) │
├──────────────┬──────────────────────┬───────────────────┤
│ │ │ │
│ packages/ │ apps/web │ apps/docs │
│ engine │ (Svelte 5 + Konva) │ (Astro Starlight)│
│ │ │ │
│ ┌─────────┐ │ ┌────────────────┐ │ ┌─────────────┐ │
│ │FSMEngine│◄┼──│ store.svelte │ │ │ Starlight │ │
│ │ DFA │ │ │ (Project) │ │ │ Docs Site │ │
│ │ types │ │ │ │ │ │ │ │
│ └─────────┘ │ └───────┬────────┘ │ └─────────────┘ │
│ │ │ │ │
│ │ ┌─────▼─────┐ │ │
│ │ │ Editor │ │ │
│ │ │ Canvas │ │ │
│ │ │ (Konva) │ │ │
│ │ └───────────┘ │ │
└──────────────┴──────────────────────┴───────────────────┘
Key design decisions:
- The engine package has zero UI dependencies — it can be consumed independently as a library.
- The web app uses Svelte 5 runes (
$state,$effect,$derived) withSvelteMapfor fine-grained reactive state management. - Canvas rendering is powered by Konva via
svelte-konva, giving hardware-accelerated 2D drawing. - The engine's internal
Map<number, State>store is shared by reference with the Svelte frontend's reactiveSvelteMap, keeping both layers synchronized with minimal overhead.
| Layer | Technology |
|---|---|
| Runtime | Bun |
| Language | TypeScript 5 (strict mode) |
| Frontend Framework | Svelte 5 (Runes mode) |
| Canvas | Konva + svelte-konva |
| Styling | Tailwind CSS 4 + shadcn-svelte |
| Graph Layout | @dagrejs/dagre |
| Icons | @lucide/svelte |
| Bundler | Rolldown Vite |
| Documentation | Astro + Starlight |
| Linting | ESLint 9 + Prettier |
| Git Hooks | Husky + lint-staged |
- Bun ≥ 1.3 (used as both package manager and runtime)
# Clone the repository
git clone https://github.com/karthik-saiharsh/fsm-engine.git
cd fsm-engine
# Install all workspace dependencies
bun install# Start the web editor (apps/web)
cd apps/web
bun run dev
# Start the documentation site (apps/docs)
cd apps/docs
bun run devcd apps/web
bun run build
bun run preview # Preview the production build locallyfsm-engine/
├── packages/
│ └── engine/ # @fsm/engine — headless FSM library
│ ├── FSMEngine.ts # Base engine: states, transitions, save/load
│ ├── DFA.ts # DFA subclass: alphabet enforcement, string validation
│ ├── index.ts # Public API barrel export
│ └── utils/
│ └── types.ts # Shared types: State, Transition, EngineTypes
│
├── apps/
│ ├── web/ # Svelte 5 visual editor
│ │ ├── src/
│ │ │ ├── App.svelte # Root component: Launch screen ↔ Editor
│ │ │ ├── app.css # Design tokens (oklch), Geist font, Tailwind
│ │ │ └── lib/
│ │ │ ├── brain/ # State management layer
│ │ │ │ ├── store.svelte.ts # Project singleton (engine + UI state)
│ │ │ │ ├── extras.svelte.ts # Secondary reactive stores
│ │ │ │ └── types.ts # Frontend-specific types
│ │ │ └── components/ # UI components
│ │ │ ├── Editor.svelte # Konva canvas + rendering loop
│ │ │ ├── Dock.svelte # Bottom toolbar (Add/Remove/Connect/Zoom)
│ │ │ ├── Launch.svelte # Welcome / project picker screen
│ │ │ ├── Alert.svelte # Notification overlay
│ │ │ ├── editor/ # TopBar, MachinePicker
│ │ │ ├── popus/ # NodeCustomizer, TransitionCustomizer, etc.
│ │ │ ├── generic/ # Window, ScreenSizeFallback
│ │ │ └── ui/ # shadcn-svelte primitives
│ │ ├── index.html
│ │ ├── vite.config.ts
│ │ └── svelte.config.js
│ │
│ └── docs/ # Astro Starlight documentation site
│ ├── astro.config.mjs
│ └── src/content/docs/
│
├── package.json # Workspace root (bun workspaces)
├── tsconfig.json # Shared TypeScript config
├── eslint.config.mts # ESLint flat config
├── .prettierrc # Prettier formatting rules
└── .husky/pre-commit # Pre-commit lint hook
The @fsm/engine package exposes two main classes that can be used independently of the UI:
import { FSMEngine } from "@fsm/engine";
const fsm = new FSMEngine("MyStateMachine");
// Add states (returns a numeric reference ID)
const s0 = fsm.addState("q0");
const s1 = fsm.addState("q1");
const s2 = fsm.addState("q2");
// Mark state types
fsm.setStart(s0);
fsm.setEnd(s2);
// Add transitions
fsm.addTransition(s0, s1, "a");
fsm.addTransition(s1, s2, "b");
fsm.addTransition(s1, s1, "a"); // self-loop
// Query the machine
fsm.searchStates("q1"); // → [1]
fsm.searchTransition(s0, s1); // → [0]
fsm.getTransitionsOn("a"); // → [0, 2]
// Serialize / Deserialize
const snapshot = fsm.saveProject();
fsm.loadProject(snapshot);import { DFA } from "@fsm/engine";
const dfa = new DFA("BinaryStrings");
// Define language alphabet
dfa.addAlphabets("0", "1");
// Build states
const q0 = dfa.addState("q0");
const q1 = dfa.addState("q1");
dfa.setStartState(q0);
dfa.endState.add(q1);
// Add deterministic transitions
dfa.addTransition(q0, q1, "1");
dfa.addTransition(q0, q0, "0");
dfa.addTransition(q1, q1, "0");
dfa.addTransition(q1, q0, "1");
// Validate strings
dfa.validateString("1010");
// → { path: [0, 1, 1, 0], accepted: true }DFA enforcement: Adding a duplicate transition on the same alphabet from the same state will throw an error, guaranteeing determinism at the API level.
Contributions are welcome and greatly appreciated! Here's how to get involved:
- Fork the repository
- Create a feature branch (
git checkout -b feat/amazing-feature) - Commit your changes (
git commit -m 'feat: add amazing feature') - Push to your branch (
git push origin feat/amazing-feature) - Open a Pull Request
- Run
bun run lintbefore submitting to ensure code quality - Discuss larger architectural changes in an issue first
- Follow Conventional Commits for commit messages
- Pre-commit hooks (Husky + lint-staged) will auto-lint staged files
- Interactive canvas editor with zoom & pan
- Add / Remove / Connect states & transitions
- State types: Start, Intermediate, End
- Project export / import (
.fsmJSON format) - Export canvas as PNG (1×, 2×, 3×)
- Auto layout (Dagre)
- Dark / Light theme
- DFA string validation with path tracing
- Transition table generation & display
- NFA → DFA subset construction
- DFA minimization
- Undo / Redo
- Validation & error hints (unreachable states, dead states)
- Regular expression → NFA conversion
- Keyboard shortcuts & accessibility improvements
- Collaborative editing
This project is licensed under the GNU General Public License v3.0.
Copyright (C) 2026 Illindala Karthik Saiharsh
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Built with ♥ by Karthik Saiharsh and contributors
Report Bug · Request Feature or Ask/Share Something · Live Demo