ai-tools/xstate/SKILL.md
# XState v5 Quick Reference ## How to Look Up API Details For complete function signatures, types, and interfaces, **grep `api-reference.md`** — do NOT read it in full (12k+ lines). Example: ``` Grep pattern="createActor" path="~/.claude/skills/xstate/api-reference.md" output_mode="content" -C 5 ``` Then use `Read` with `offset`/`limit` to get the full section. This is the primary way to get precise technical info when the quick reference below isn't enough. ## Design Workflow Recommended
npx skillsauth add randyhaylor/enhanceclaude ai-tools/xstateInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
For complete function signatures, types, and interfaces, grep api-reference.md — do NOT read it in full (12k+ lines). Example:
Grep pattern="createActor" path="~/.claude/skills/xstate/api-reference.md" output_mode="content" -C 5
Then use Read with offset/limit to get the full section. This is the primary way to get precise technical info when the quick reference below isn't enough.
Recommended approach for designing a state machine before writing code:
createActor + send + getSnapshotA state machine is pure structure (string references) with implementations wired in separately at creation time. This separation keeps machines serializable, testable, and portable.
import { setup } from 'xstate';
const machine = setup({
actions: { onEnterIdle: () => {}, notifyUser: () => {} },
guards: { isValid: () => true },
actors: { fetchData: fromPromise(async ({ input }) => { /* async work */ }) },
}).createMachine({
id: 'example',
context: { count: 0, data: null }, // reactive data
initial: 'idle',
states: {
idle: {
entry: 'onEnterIdle', // action on state enter
exit: 'cleanUp', // action on state exit
on: {
SUBMIT: {
target: 'loading',
guard: 'isValid', // transition only if guard returns true
actions: 'notifyUser', // action fired during transition
},
},
},
loading: {
invoke: {
src: 'fetchData', // async actor (service)
input: ({ context }) => context,
onDone: { target: 'idle', actions: assign({ data: ({ event }) => event.output }) },
onError: 'error',
},
},
error: { on: { RETRY: 'loading' } },
},
});
| Concept | Purpose | Defined via |
|---------|---------|-------------|
| States | Finite modes the system can be in | states: {} |
| Events | Triggers that cause transitions | on: { EVENT_NAME: ... } |
| Context | Extended (infinite) data carried by the machine | context: {}, mutated with assign() |
| Actions | Fire-and-forget side effects (entry/exit/transition) | entry, exit, actions |
| Actors | Async work: promises, observables, other machines | invoke: { src: '...' } |
| Guards | Boolean conditions gating transitions | guard: 'guardName' |
Rule of thumb: if you need onDone/onError, it is an actor.
Use fromCallback for ongoing subscriptions (e.g. Firestore onSnapshot) that send events back to the machine. The listener starts when the state is entered and is automatically cleaned up when the state exits.
import { setup, fromCallback } from 'xstate';
const machine = setup({
actors: {
watchPlayers: fromCallback(({ input, sendBack }) => {
// Start listener — sendBack pushes events to the parent machine
const unsub = onSnapshot(collection(input.db, 'players'), (snap) => {
snap.docChanges().forEach((change) => {
if (change.type === 'added') {
sendBack({ type: 'PLAYER_JOINED', id: change.doc.id, data: change.doc.data() });
}
});
});
// Return cleanup function — called automatically when state exits
return () => unsub();
}),
},
}).createMachine({
states: {
lobby: {
invoke: {
src: 'watchPlayers',
input: ({ context }) => ({ db: context.db }),
},
on: {
PLAYER_JOINED: { actions: 'addPlayer' },
},
},
},
});
Key points:
sendBack(event) sends events to the parent machinereceive(handler) listens for events FROM the parent (optional)machines/
game/
machine.js # createMachine definition (pure structure, string refs only)
actions.js # { onEnterIdle: () => {...}, notifyUser: () => {...} }
actors.js # { fetchData: fromPromise(...) }
guards.js # { isValid: ({ context }) => context.value > 0 }
index.js # wires implementations into machine, exports actor
import { createActor } from 'xstate';
import { machine } from './machine.js';
// machine already has implementations from setup() — or override:
const actor = createActor(machine, { input: { /* initial input */ } });
actor.start();
The machine definition references actions/guards/actors by string name. Implementations are provided in setup(). This means the machine structure is pure data; swap implementations for testing or different environments.
const machine = setup({
types: {} as {
context: { count: number; user: User | null };
events: { type: 'FETCH'; id: string } | { type: 'RESET' };
input: { initialId: string };
},
// ...
}).createMachine({ /* ... */ });
The {} as Type pattern is intentional — runtime value is ignored, types are inferred.
// Override implementations for tests
const testMachine = machine.provide({
actors: { fetchData: fromPromise(async () => mockData) },
});
const actor = createActor(testMachine);
actor.start();
actor.send({ type: 'FETCH', id: '1' });
expect(actor.getSnapshot().value).toBe('loaded');
// Async: wait for state
import { waitFor } from 'xstate';
await waitFor(actor, (snap) => snap.value === 'done');
| v4 | v5 |
|----|-----|
| cond | guard |
| services | actors |
| event.data | event.output |
| .withConfig() | .provide() |
| send() | raise() (self) / sendTo() (other) |
assign() — only context updatescontext: { isLoading: true } — use actual state nodessetup() — enables provide() for testingtools
Workaround for agent teams in VS Code extension where TeamCreate teammates cannot execute tools. Uses an echo-back-and-resume pattern where agents return tool requests instead of executing them directly.
development
Format documentation, READMEs, and structured text using header hierarchy where each level stands alone. Use when creating docs, research notes, summaries, or when user requests 'scannable,' 'well-structured,' 'skimmable,' or 'readable at multiple depths' output. Applies to markdown, technical specs, and any hierarchical text formatting.
development
Enforce strict Test-Driven Development workflow: write one test, make it pass, verify, then proceed. Prevents over-implementation and ensures code matches requirements exactly. Use when implementing new features, adding settings, or building functionality incrementally.
development
Core principles for writing clean, disciplined Vue 3 apps. Invoke when reviewing architecture, adding features, or evaluating component design.