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

state-machine-skill

v1.3.0

Published

AI agent skill: model UI components as finite state machines before coding. Claude Code · Cursor · Windsurf · OpenCode

Readme

🔄 state-machine

Model UI behavior before you code it. Eliminate impossible states before they exist.

npm version License: MIT Works with Claude Code Works with Cursor Works with Windsurf Works with OpenCode

Compatible with Claude Code · Cursor · Windsurf · OpenCode · Any AI coding agent


Why state machines for UI?

Three booleans. Eight possible states. Five of them impossible.

const [isLoading, setIsLoading] = useState(false);
const [isError, setIsError] = useState(false);
const [isSuccess, setIsSuccess] = useState(false);

| isLoading | isError | isSuccess | Result | |-----------|---------|-----------|--------| | ❌ | ❌ | ❌ | Idle — valid | | ✅ | ❌ | ❌ | Loading — valid | | ❌ | ✅ | ❌ | Error — valid | | ❌ | ❌ | ✅ | Success — valid | | ✅ | ✅ | ❌ | IMPOSSIBLE — loading and erroring at once | | ✅ | ❌ | ✅ | IMPOSSIBLE — loading and succeeded at once | | ❌ | ✅ | ✅ | IMPOSSIBLE — error and success at once | | ✅ | ✅ | ✅ | IMPOSSIBLE — all three at once |

Every impossible state is a bug waiting to happen. A race condition, a missed reset, a late network response — and your UI shows a spinner over content, or an error over a success view.

A state machine replaces N booleans with one state variable. Instead of 2^N combinations (most invalid), you get exactly N states — all valid. If a state isn't in the alphabet, the component cannot enter it.


How it works: single-command workflow

┌──────────┐     ┌──────────────┐     ┌──────────────────┐     ┌──────────┐
│ Describe │────>│  model verb  │────>│ Agent asks:      │────>│  Ready   │
│ behavior │     │  (blueprint) │     │ "Implement now?" │     │ component│
│ in NL    │     │  3 sections  │     │  → picks folder  │     │  shipped │
└──────────┘     └──────────────┘     │  → generates     │     └──────────┘
                                      │    code + tests  │
                                      └──────────────────┘

model command: You describe the component behavior in natural language. The agent produces 3 sections: Behavior Specification, validated JSON contract, and ASCII diagram. Then it asks if you want to generate the component code now.

Implementation: If you accept, the agent scans your project directories, confirms the target folder with you, injects the micro-runtime (createLightMachine), generates the component code wired to the state machine, and writes unit tests covering every transition.


Install

| Agent | Command | |-------|---------| | Any agent | npx skills add pauloriveross/state-machine-skill | | Claude Code | npx skills add pauloriveross/state-machine-skill | | Cursor | Copy SKILL.md.cursor/rules/state-machine.mdc, references/.cursor/rules/state-machine/ | | Windsurf | cp SKILL.md .windsurf/rules/state-machine.md + cp -r references/ .windsurf/rules/state-machine-references/ | | OpenCode | cp -r * .agent/skills/state-machine/ (or just open the repo — auto-detected) | | Manual | Copy SKILL.md + references/ into your agent's skill directory |


Two verbs. One guarantee.

model — From natural language to validated blueprint (with optional implementation)

Input: A natural language description of component behavior.

Output: 3 sections (Behavior Specification, validated JSON contract, ASCII diagram), then an auto-prompt to implement.

How the agent executes it:

  1. Translates your description into a hierarchical JSON conforming to schemas/fsm.schema.json
  2. Saves to .state-machine/temp-model.json
  3. Runs node scripts/validate-model.js .state-machine/temp-model.json
  4. If validation fails, fixes the JSON and re-runs — no intermediate output, no asking for help
  5. On success, outputs the 3-section markdown block
  6. Asks: "Do you want me to generate the component code from this model?"
  7. If yes, scans your project directories, asks which folder, generates code + tests

Example:

> state-machine model a toggle that switches on and off

The agent returns:

## 1. Behavior Specification
A simple toggle switch with two states — On and Off. Clicking the toggle flips between the two states.

## 2. Structural Contract (`model.json`)

```json
{
  "id": "toggle",
  "initial": "Off",
  "states": {
    "Off": { "type": "atomic", "on": { "TOGGLE": { "target": "On" } } },
    "On":  { "type": "atomic", "on": { "TOGGLE": { "target": "Off" } } }
  }
}
```

## 3. Transition Diagram (ASCII)

```
┌────────┐  TOGGLE  ┌────────┐
│  Off   │─────────>│   On   │
└────────┘<─────────└────────┘
             TOGGLE
```

✅ All 13 gates passed.

Then the agent asks: "Do you want me to generate the component code from this model? I can place it in your project."

If you accept, it scans for project folders, confirms the target directory, and generates the implementation.


Implementation (auto-prompted after model)

When you accept the post-model prompt, the agent:

  1. Scans your project for package.json, src/, components/, app/ directories
  2. Asks you to confirm the target folder
  3. Copies src/core/fsm.ts into that folder as fsm.ts (if not already present)
  4. Generates the UI component wired to createLightMachine
  5. Writes unit tests that simulate 100% of transitions

Every component carries a guarantee:

/* state-machine: Closed|Loading|Success|Error : TRIGGER|FETCH_SUCCESS|FETCH_ERROR|CLOSE|RETRY */

This component cannot enter a state that was not explicitly modeled.


audit — Detect impossible states in legacy code

Input: A file path to an existing component using boolean flags (useState, ref, boolean fields).

Output: A structured JSON report with detected flags, combinatorial complexity, and impossible state analysis.

How the agent executes it:

  1. Scans the file for reactive boolean variables (const [isXxx, ...], const [hasXxx, ...])
  2. Runs node scripts/audit-processor.js file.tsx
  3. Computes the 2^n combinatorial matrix
  4. Identifies which combinations have no visual representation or coherent logic
  5. Outputs a ranked punch list

Example:

> state-machine audit components/ui/OldModal.tsx

Output:

{
  "detectedFlags": ["isLoading", "isError", "isSuccess"],
  "complexity": "3 flags = 8 combinaciones posibles",
  "impossibleStatesDetected": [
    {
      "combination": { "isLoading": true, "isError": true },
      "severity": "CRITICAL",
      "description": "Loading spinner and error message simultaneously"
    }
  ]
}

Model format: two ways to write your JSON

The linter auto-detects which format you're using.

Flat format (simple)

{
  "states": ["Closed*", "Loading", "Success", "Error"],
  "transitions": [
    { "From": "Closed", "Event": "TRIGGER", "To": "Loading" },
    { "From": "Loading", "Event": "FETCH_SUCCESS", "To": "Success" },
    { "From": "Loading", "Event": "FETCH_ERROR", "To": "Error" }
  ]
}

Hierarchical format (schema — per schemas/fsm.schema.json)

Supports compound states, guards, actions, lifecycle hooks, and cross-hierarchy targets.

{
  "id": "async-modal",
  "initial": "Closed",
  "states": {
    "Closed": {
      "type": "atomic",
      "on": {
        "TRIGGER": { "target": "Loading" }
      }
    },
    "Loading": {
      "type": "atomic",
      "onEnter": ["fetchData"],
      "on": {
        "FETCH_SUCCESS": { "target": "Success", "actions": ["storeData"] },
        "FETCH_ERROR": { "target": "Error", "actions": ["logError"] }
      }
    },
    "Success": {
      "type": "atomic",
      "on": {
        "CLOSE": { "target": "Closed", "actions": ["clearData"] }
      }
    },
    "Error": {
      "type": "atomic",
      "on": {
        "RETRY": { "target": "Loading" },
        "CLOSE": { "target": "Closed" }
      }
    }
  }
}

For compound (nested) states with cross-hierarchy targets:

{
  "id": "auth-flow",
  "initial": "Unauthenticated",
  "states": {
    "Unauthenticated": {
      "type": "compound",
      "initial": "Idle",
      "states": {
        "Idle": { "type": "atomic", "on": { "LOGIN": { "target": "Authenticating" } } },
        "Authenticating": { "type": "atomic" },
        "MfaRequired": { "type": "atomic" }
      }
    },
    "Authenticated": {
      "type": "compound",
      "initial": "Active",
      "states": {
        "Active": {
          "type": "atomic",
          "on": {
            "LOGOUT": { "target": "#auth-flow.Unauthenticated.Idle", "actions": ["clearSession"] }
          }
        }
      }
    }
  }
}

4 worked examples

Each example demonstrates the model output in the unified 3-section format: behavior specification → structural JSON contract → ASCII diagram.

┌────────┐ TRIGGER  ┌─────────┐ FETCH_SUCCESS ┌─────────┐
│ Closed │─────────>│ Loading │──────────────>│ Success │
└────────┘          └────┬────┘               └────┬────┘
                         │                         │ CLOSE
                   CLOSE │                         v
                         │                    ┌─────────┐
                         └───────────────────>│  Closed  │
                                              └─────────┘

File: examples/modal.md

┌────────┐  NEXT   ┌────────┐  NEXT   ┌────────┐  SUBMIT
│ Step1  │────────>│ Step2  │────────>│ Step3  │────────────┐
└───┬────┘         └───┬────┘         └────────┘            │
    │ PREV              │ PREV                               │
    └───────────────────┘                                    │
                              ┌────────────┐                 │
                              │ Submitting │◄────────────────┘
                              └──────┬─────┘
                         ┌───────────┼──────────┐
                         v           v          │
                    ┌─────────┐ ┌─────────┐     │
                    │ Success │ │  Error  │─────┘
                    └─────────┘ └────┬────┘
                                     │ RETRY
                                     v
                                 ┌────────────┐
                                 │ Submitting │
                                 └────────────┘

File: examples/multistep-form.md

     ┌────────┐      ┌────────────┐ CONFIRM ┌────────┐
     │  Off   │─────>│ PendingOn  │───────>│   On   │
     └───┬────┘      └──────┬─────┘        └───┬────┘
         │                  │ REJECT            │
         │                  v                   │
         │              ┌───────┐               │
         └─────────────>│ Error │<──────────────┘
                        └───┬───┘
                      RETRY  │  DISMISS
                             v
                         ┌────────┐
                         │Pending │
                         └────────┘

File: examples/toggle-async.md

┌──────────────────────────────────────────┐
│ Unauthenticated                          │
│  ┌────────┐  LOGIN     ┌──────────────┐ │
│  │  Idle  │───────────>│ Authenticating│ │
│  └────────┘<────────────│              │ │
│       ^       LOGIN_ERR └──────┬───────┘ │
│       │ MFA_CANCEL    LOGIN_MFA│         │
│       │                 ┌──────v───────┐ │
│       └─────────────────│ MfaRequired  │ │
│                  MFA_SUBMIT(hasMfaCode) │ │
└──────────────────────────────────────────┘
                    │ LOGIN_SUCCESS
                    v
┌──────────────────────────────────────────┐
│ Authenticated                            │
│  ┌────────┐  REFRESH_TOKEN ┌────────────┐│
│  │ Active │───────────────>│ Refreshing  ││
│  └───┬────┘<───────────────│            ││
│      │    REFRESH_SUCCESS  └──────┬─────┘│
│      │                    REFRESH_FAIL   │
│      └──────────────────────────────┘    │
│  LOGOUT → Unauthenticated.Idle           │
│  SESSION_EXPIRED → Unauthenticated.Idle  │
└──────────────────────────────────────────┘

File: examples/auth-flow.md


Scripts

| Script | Purpose | |--------|---------| | node scripts/validate-model.js model.json | Validate model against gates 01–13 + structural pre-check | | node scripts/validate-model.js model.json --light | Fast-track: skip warnings, compact diagram | | node scripts/ascii-viz.js model.json | Render ASCII transition diagram only | | node scripts/audit-processor.js file.tsx | Analyze boolean flags in legacy code |

All scripts are zero-dependency. Exit code 0 = valid, 1 = invalid with gate report.


Project structure

| File | Purpose | |------|---------| | SKILL.md | Mandatory execution protocol — tells the AI agent exactly how to handle each verb | | src/core/fsm.ts | Injectable micro-runtime (createLightMachine) — copied into your project during implementation | | schemas/fsm.schema.json | Canonical JSON Schema for hierarchical FSM models | | scripts/validate-model.js | Model validator — 13 gates + graph structural check, auto-detects flat/hierarchical format | | scripts/ascii-viz.js | Terminal ASCII transition diagram renderer | | scripts/audit-processor.js | Boolean flag analyzer — detects impossible state combinations in legacy code | | references/verb-dispatch.md | Exact output format specification for model and audit verbs | | references/slop-gates.md | 38 validation gates (model 1–13, code 14–23, audit 24–38) | | references/state-theory.md | FSM/HFSM fundamentals applied to UI components | | references/component-patterns.md | 12 canonical UI patterns with pre-built models and invariants | | references/impossible-states.md | 35 anti-patterns with elimination strategies | | references/xstate-compat.md | Full mapping to XState v5 for every pattern | | references/framework-adapters.md | useMachine adapter for React, Vue, Svelte, Vanilla JS | | examples/ | 4 worked examples in unified format (modal, toggle, form, auth) |


XState v5 compatible

Every generated model maps directly to XState v5. Use the machine in any framework via @xstate/react, @xstate/vue, or @xstate/svelte.

import { setup } from 'xstate';

export const modalMachine = setup({
  types: { /* ... */ },
  actions: { fetchData: () => fetch('/api/data') },
}).createMachine({
  id: 'modal',
  initial: 'Closed',
  states: {
    Closed: { on: { TRIGGER: 'Loading' } },
    Loading: {
      on: {
        FETCH_SUCCESS: 'Success',
        FETCH_ERROR: 'Error',
        CLOSE: 'Closed',
      },
    },
    Success: { on: { CLOSE: 'Closed' } },
    Error: { on: { CLOSE: 'Closed', RETRY: 'Loading' } },
  },
});

See references/xstate-compat.md.


Framework support

| Framework | Mechanism | |-----------|----------| | React | useMachine(config, impl) via useState + useRef | | Vue | useMachine(config, impl) via ref + readonly | | Svelte | useMachine(config, impl) via writable store | | Vanilla JS | createMachine(config, impl) closure factory | | XState v5 | setup() + createMachine() |


Contributing

  1. Add a new component pattern to references/component-patterns.md
  2. Add corresponding anti-patterns to references/impossible-states.md
  3. Add validation gates to references/slop-gates.md
  4. Run node scripts/validate-model.js on the example models

License

MIT © 2026