npx skills add ...
npx skills add seed-hypermedia/seed --skill xstate
Use when designing, implementing, reviewing, or migrating XState v5 state machines, statecharts, actors, invoked services, @xstate/react hooks, or Stately diagrams in TypeScript, especially in a codebase that already imports xstate or @xstate/react.
npx skills add seed-hypermedia/seed --skill xstate
Use this skill for state machine and statechart engineering first and API correctness second.
This skill is v5-only. When examples, blog posts, answers, or local code smell v4-ish, translate them rather than mixing versions. Prefer local repo code and official v5 docs over generic memory.
In Seed, the main repo currently uses xstate@5.19.2 and @xstate/react@4.1.3, especially under
frontend/packages/shared/src/models/**. Read nearby Seed machines and hooks before editing them, particularly
document-machine.ts, document-card-cleanup-machine.ts, use-document-machine.ts, use-editor-gate.ts, and React
usage of useActorRef(...), useMachine(...), and useSelector(...).
Your job:
xstate and @xstate/storeassign(...) plus fromPromise(...)@xstate/react, @xstate/vue, @xstate/svelte, or @xstate/solid when neededIn an existing codebase:
package.json files and imports.Choose a mode before writing code:
For migration and local edits, do not introduce setup(...), named implementations, object-form guards/actions, tags,
or actor decomposition unless they are required for correctness, significantly reduce local complexity, or were
explicitly requested.
Prefer @xstate/store when the domain is simple event-based state management:
Simple fetching or mutation logic can still fit @xstate/store if it does not need machine-level orchestration.
In Seed, verify @xstate/store is installed before recommending or importing it. If it is not installed, suggest it
only when the simpler store model justifies adding a dependency.
Prefer XState when the domain has one or more of these:
If the problem is too simple for a machine, say so plainly and recommend @xstate/store.
When requirements are fuzzy:
When requirements are already clear, keep the sketch short or implicit and move to code.
Do not over-refactor existing code for conceptual purity. Improve the model where it matters, but preserve working structure when a larger rewrite is not justified.
Use these questions before writing or revising code:
form.submitted or
order.confirmed.fromPromise(...) actually the right actor, or is this a callback/subscription protocol that should send events
back over time?'loading', 'error', or 'dirty'?snapshot.matches(...), tags, or snapshot.can(...) instead of extra booleans?For new code, prefer these patterns unless the local codebase has a strong reason not to:
setup({...}).createMachine({...}).actions, guards, actors, and delays in setup(...).{ target: 'next' } over shorthand when it improves consistency.actions, even for a single action, when that keeps the shape consistent.{ type: 'track' } or { type: 'isValid', params: ... } when the intent is
reusable or named.tags: [] for UI semantics and cross-cutting state meaning.params instead of reaching into event directly.assign(...) as the only action pattern. For typed reusable logic beyond simple context updates,
consider setup-scoped helpers from a const machineSetup = setup(...) object, such as
machineSetup.createAction(...), machineSetup.enqueueActions(...), and machineSetup.emit(...), or named action
objects when they fit the behavior better.assign(...) action updates multiple context properties, every property updater shares the same params
type. Use one coherent params object for all updated fields, or split the work into separate named actions. Do not mix
incompatible params shapes inside one assigner.event and the type is not obvious, prefer assertEvent(...) for narrowing.as any and other loose casts in examples and final code unless there is no cleaner local option.fromPromise(...) for one request/one result, and prefer fromCallback(...)
for subscriptions, timers, external callbacks, and multi-event protocols that send events back over time.emit(...) when the machine should notify the surrounding system directly.Keep event naming consistent. The . in event names is useful and meaningful, including for partial event descriptors
and clearer domain grouping. See the official docs on events and transitions and
TypeScript narrowing with assertEvent(...).
See references/examples.md for canonical code shapes, references/advanced-patterns.md when the task involves typed
actions beyond assign(...), callback actors, emitted events, or persistence/hydration, and
references/observables-and-inspection.md for fromObservable(...), inspection, and browser inspector patterns.
Prefer letting the machine drive UI behavior:
snapshot.matches(...) for finite mode checkssnapshot.can(...) to drive whether an event is currently validIf a component owns a small local actor, useMachine(...) is often enough.
If an actor is shared, long-lived, or performance-sensitive:
useSelector(...)See references/adapters.md for concise adapter guidance, and references/react.md for React hook selection, input
wiring, nested-state matching, and custom hook patterns.
When reviewing or fixing XState code, look for:
matches(...), tags, or selectors@xstate/store when the problem is simpleassign(...) actions whose property updaters implicitly expect different params shapesAvoid pushing decomposition too far. Actor boundaries are useful when they improve clarity, ownership, or concurrency. Do not split for its own sake.
When legacy code is present, translate toward v5 gradually and locally unless the user asked for a broader migration.
Common migration targets include:
cond -> guardschema -> typesservices -> actorsinterpret(...) -> createActor(...)({ context, event })For migration tasks:
interpret-style
implementation overrides into useMachine(...)Use references/v4-to-v5-quick-ref.md for quick translation patterns.
When persistence or hydration matters:
actor.getPersistedSnapshot() for saving machine statecreateActor(machine, { snapshot }) or adapter/provider snapshot options where availableSee references/advanced-patterns.md for concrete persistence and hydration patterns.
Do not introduce testing guidance unless the user asks about testing or requests tests.
If they do ask, keep it brief:
If a link would help, point them to the XState testing and graph/path generation docs.
Stately Studio and inspection tools can help with design, debugging, and communication, but they are optional. Mention them when the user is designing a machine, wants visualization, or needs better debugging visibility.
Prefer this output shape:
Prefer fewer complete files over a larger but partially wired example. Do not sketch extra shell files unless they are fully wired.
See references/examples.md for more canonical examples that can grow over time.
Before you answer, do a quick compile-minded pass:
send(...) and snapshot.can(...) call uses real event objectsactors/fromPromise and invoke.input, not legacy shapes..., or pseudocode in code fences that are meant to be runnableIf a complete multi-file answer is getting bulky, remove the shell file and keep the smaller set of files that still demonstrates the pattern correctly.**