skills/state-machine-designer/SKILL.md
Design and implement finite state machines and statecharts for complex UI flows using XState v5 and Zustand. Activate on: multi-step forms, complex UI state, wizard flows, auth flows, statechart, XState. NOT for: simple boolean toggles (use React useState), server state (use data-fetching-strategist).
npx skillsauth add curiositech/windags-skills state-machine-designerInstall 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.
Model complex UI flows as finite state machines and statecharts using XState v5, eliminating impossible states and race conditions.
Activate on: multi-step wizards, authentication flows, payment checkout, complex form state, drag-and-drop orchestration, media player controls, XState, statechart, createMachine.
NOT for: simple boolean toggles (use useState). Server/async data caching -- use data-fetching-strategist. Global app state without transitions -- use Zustand directly.
setup() + createMachine() with @xstate/react useMachine hook.createActor in unit tests to verify every path.| Domain | Technologies | Key Patterns |
|--------|-------------|--------------|
| State Machines | XState v5, setup() API | Flat machines for simple flows |
| Statecharts | XState hierarchical/parallel states | Nested states, history states |
| UI Binding | @xstate/react useMachine, useSelector | React integration, selective re-renders |
| Lightweight State | Zustand with state enum pattern | When XState overhead is too much |
| Visualization | Stately.ai editor, @stately-ai/inspect | Visual debugging of live machines |
| Testing | createActor, getSnapshot | Deterministic transition testing |
setup()import { setup, assign } from 'xstate';
const checkoutMachine = setup({
types: {
context: {} as {
items: CartItem[];
address: Address | null;
paymentMethod: PaymentMethod | null;
error: string | null;
},
events: {} as
| { type: 'SET_ADDRESS'; address: Address }
| { type: 'SET_PAYMENT'; method: PaymentMethod }
| { type: 'SUBMIT' }
| { type: 'BACK' }
| { type: 'RETRY' },
},
guards: {
hasAddress: ({ context }) => context.address !== null,
hasPayment: ({ context }) => context.paymentMethod !== null,
},
}).createMachine({
id: 'checkout',
initial: 'cart',
context: { items: [], address: null, paymentMethod: null, error: null },
states: {
cart: { on: { SUBMIT: { target: 'address', guard: 'hasItems' } } },
address: { on: { SET_ADDRESS: { actions: assign({ address: (_, e) => e.address }), target: 'payment' }, BACK: 'cart' } },
payment: { on: { SET_PAYMENT: { actions: assign({ paymentMethod: (_, e) => e.method }), target: 'review' }, BACK: 'address' } },
review: { on: { SUBMIT: 'processing', BACK: 'payment' } },
processing: {
invoke: { src: 'processPayment', onDone: 'success', onError: { target: 'error', actions: assign({ error: (_, e) => e.data.message }) } },
},
success: { type: 'final' },
error: { on: { RETRY: 'processing', BACK: 'review' } },
},
});
When XState is overkill but useState booleans create impossible states:
import { create } from 'zustand';
type AuthState = 'idle' | 'authenticating' | 'authenticated' | 'error' | 'mfa_required';
interface AuthStore {
state: AuthState;
user: User | null;
error: string | null;
login: (creds: Credentials) => Promise<void>;
submitMfa: (code: string) => Promise<void>;
logout: () => void;
}
const useAuthStore = create<AuthStore>((set, get) => ({
state: 'idle',
user: null,
error: null,
login: async (creds) => {
if (get().state !== 'idle' && get().state !== 'error') return; // guard
set({ state: 'authenticating', error: null });
try {
const res = await api.login(creds);
if (res.mfaRequired) set({ state: 'mfa_required' });
else set({ state: 'authenticated', user: res.user });
} catch (e) {
set({ state: 'error', error: e.message });
}
},
logout: () => set({ state: 'idle', user: null }),
}));
┌────────────────────────────────────────────────┐
│ checkout │
│ │
│ [cart] ──SUBMIT──> [address] ──SET_ADDR──> │
│ ^ │ │
│ └───BACK───────────┘ │
│ │
│ [payment] ──SET_PAY──> [review] ──SUBMIT──> │
│ ^ │ │
│ └───BACK───────────────┘ │
│ │
│ [processing] ──onDone──> [success] (final) │
│ │ │
│ └──onError──> [error] ──RETRY──> │
│ │ (back to processing)│
│ └──BACK──> [review] │
└────────────────────────────────────────────────┘
isLoading && !isError && isSubmitted creates impossible state combinations. Use a single state enum or machine instead.actions or invoke for side effects.useState<boolean> for trivially simple state.success to cart)RETRY or BACK)createActor covering happy path + error path + edge casesuseSelector for selective re-renders (not full context subscription)data-ai
license: Apache-2.0 NOT for unrelated tasks outside this domain.
development
Use when designing caching strategies (cache-aside, write-through, write-behind), implementing distributed locks, building rate limiters, leaderboards, real-time streams (XADD/consumer groups), pub/sub, or tuning eviction policies. Triggers: thundering-herd on cache miss, dogpile on key expiry, Redlock vs SET-NX-PX choice, sliding-window rate limiter, hot-key on a single cluster slot, big-key blowup, MULTI/EXEC across slots, KEYS in production. NOT for Redis Cluster operations/admin (different domain), embedded KV (SQLite, leveldb), in-process LRU caches, or Memcached.
tools
Drawing the `'use client'` boundary correctly in React Server Components apps (Next.js App Router, RSC frameworks) — leaf-pushing, slot composition, serialization rules, and environment poisoning prevention. Grounded in react.dev and Next.js 16 docs.
development
Use when designing rate limiting for an API, choosing between token bucket / sliding window / leaky bucket / fixed window, implementing it in Redis, deciding edge (Cloudflare/Upstash) vs origin enforcement, sizing per-user vs per-IP vs per-endpoint quotas, returning the right 429 response with Retry-After, or fixing the boundary-burst bug in fixed-window limiters. Triggers: 429 too many requests, INCR + EXPIRE, ZADD + ZREMRANGEBYSCORE + ZCARD, X-RateLimit-Remaining header, Cloudflare WAF rate limiting rules, Upstash @upstash/ratelimit, leaky bucket shaping vs policing, distributed rate limiter consistency. NOT for DDoS mitigation specifically (different scale), CAPTCHA / bot management, full WAF design, or per-user quota billing.