Limits
path() and back() are for series flows
They replay go guards and onFinal hand-offs forward from the initial state. A
parallel region has no single predecessor, and event-driven jumps and loops are
not replayable.
When the replay cannot reach the current state, path() returns just that state
and back() stays put.
toPath/toValue are series-only for the same reason — give each region its own
segments if you need URLs over a parallel state.
No history states
Re-entering a compound state goes to its initial.
Transitions are always external
Targeting the state you are in exits and re-enters it, running onExit then onEntry. There is no internal-transition flag.
This is what makes the self-transition idiom re-run guards, and it means onEntry must be idempotent.
codes: {
// idempotent: the answer self-transition re-enters this state
onEntry: 'generateRecoveryCodes',
on: { acknowledge: { target: 'codes', actions: 'patchAccount' } },
go: [{ target: 'done', cond: (ctx) => !!ctx.account.recoveryCodesAckedAt }],
}, Event names match exactly
No SCXML wildcards, no error.* prefixes.
invoke is a one-shot promise, not a concurrent session
src is awaited inline while the machine settles. No actors, no spawning, no
cancellation. It re-runs every time its state is entered.
No delayed transitions and no scheduler
Nothing happens between calls.
Non-settling machines throw
A machine whose eventless transitions never settle throws after 1000 microsteps rather than hanging.