Guide

always

Continue chart-owned decisions without an event until no eventless edge remains enabled.

An event is necessary when new information arrives. When the chart already has every fact needed for its next decision, always lets it continue without inventing another command.

Continue when there is nothing to wait for.

Some decisions need no new event. always lets the chart continue from the facts it already has until no eventless edge remains enabled.

1 · Wait for another event

The caller advances a decision the chart already knows.

Begin publishes Preparing, then the chart waits for Continue. The caller must send a second event even though no new fact is needed to choose Ready.

import { State, Statechart, States, Transitions } from "@motive/motive";
import { Schema } from "effect";

const Begin = Schema.TaggedStruct("Begin", {});
const Continue = Schema.TaggedStruct("Continue", {});

class Checkout extends State.Compound<Checkout>()("Checkout") {
  static states = States.make(() => [Idle, Preparing, Ready]);
}

class Idle extends State.Atomic<Idle>()("Idle") {
  static transitions = Transitions.make(this, ({ on }) => [
    on(Begin, Preparing),
  ]);
}

class Preparing extends State.Atomic<Preparing>()("Preparing") {
  static transitions = Transitions.make(this, ({ on }) => [
    on(Continue, Ready),
  ]);
}

class Ready extends State.Atomic<Ready>()("Ready") {}

export const CheckoutChart = Statechart.make(Checkout);

2 · Continue without an event

always moves as soon as Preparing is active.

always(Ready) is considered as soon as Preparing becomes active. The chart settles at Ready before publishing the result, so no caller has to send Continue.

import { State, Statechart, States, Transitions } from "@motive/motive";
import { Schema } from "effect";

const Begin = Schema.TaggedStruct("Begin", {});

class Checkout extends State.Compound<Checkout>()("Checkout") {
  static states = States.make(() => [Idle, Preparing, Ready]);
}

class Idle extends State.Atomic<Idle>()("Idle") {
  static transitions = Transitions.make(this, ({ on }) => [
    on(Begin, Preparing),
  ]);
}

class Preparing extends State.Atomic<Preparing>()("Preparing") {
  static transitions = Transitions.make(this, ({ always }) => [always(Ready)]);
}

class Ready extends State.Atomic<Ready>()("Ready") {}

export const CheckoutChart = Statechart.make(Checkout);

3 · Settle the whole chain

Eventless transitions continue until none remain enabled.

Preparing now leads through Validating before Ready. Both eventless transitions run in order, yet readers still receive only the settled Ready configuration.

import { State, Statechart, States, Transitions } from "@motive/motive";
import { Schema } from "effect";

const Begin = Schema.TaggedStruct("Begin", {});

class Checkout extends State.Compound<Checkout>()("Checkout") {
  static states = States.make(() => [Idle, Preparing, Validating, Ready]);
}

class Idle extends State.Atomic<Idle>()("Idle") {
  static transitions = Transitions.make(this, ({ on }) => [
    on(Begin, Preparing),
  ]);
}

class Preparing extends State.Atomic<Preparing>()("Preparing") {
  static transitions = Transitions.make(this, ({ always }) => [
    always(Validating),
  ]);
}

class Validating extends State.Atomic<Validating>()("Validating") {
  static transitions = Transitions.make(this, ({ always }) => [always(Ready)]);
}

class Ready extends State.Atomic<Ready>()("Ready") {}

export const CheckoutChart = Statechart.make(Checkout);

Waiting should wait for something

The first Checkout enters Preparing when it handles Begin, then waits for Continue before it can enter Ready.

But Continue carries no new information. Nothing outside the chart changed, no clock expired, and no work completed. The sender is being asked to advance a decision that the chart already knows how to make.

The live readout exposes the extra public stop: Begin makes Preparing visible, and Continue is required to publish Ready.

always needs no event

The second frame replaces the Continue handler with an eventless transition:

class Preparing extends State.Atomic<Preparing>()("Preparing") {
  static transitions = Transitions.make(this, ({ always }) => [
    always(Ready),
  ]);
}

As soon as Preparing becomes active, always(Ready) is eligible. It does not wait for a sender or consume an event. The chart continues to Ready from the facts already present.

Now one Begin produces one visible change from Idle to Ready. Preparing never becomes a configuration that an external reader can observe and act on.

Transient states are still states

Preparing remains real authored topology. It can own data, guards, and lifecycle behavior, and the transition through it is part of the chart's deterministic execution.

Transient means only that the state cannot be a stable stopping point while one of its eventless edges is enabled. It does not mean the state is erased from the model or replaced with an imperative function call.

The chart settles the whole chain

The final frame inserts Validating:

Preparing → Validating → Ready

Both links are always transitions. After Begin, the chart continues through the chain in authored order and publishes only the settled Ready configuration.

Eventless transitions are reconsidered after each internal step until none remain enabled. A guarded always edge may therefore become eligible because an earlier step changed active facts.

Stabilization must terminate

A configuration with no enabled eventless transition is stable: the chart can honestly wait for new input. An accidental cycle made entirely of enabled always edges can never reach that point.

Motive enforces a stabilization safety boundary and fails a non-terminating automatic cycle instead of allowing one instance to run forever.

Use always when the next choice follows entirely from chart facts already present. If a person, service, timer, activity, or child must supply new information, that arrival deserves its own event or mounted citizen.

Test the stabilization

  1. Add one more transient validation state and confirm that Begin still reveals only Ready.
  2. Guard the Preparing decision and give its fallback a different stable target.
  3. Point Ready back to Preparing with another eventless edge and observe the safety boundary.