Atomic states are the leaves of a statechart. They name situations that contain no child states. Atomic says nothing about how long the state lasts, whether work runs there, or whether the chart is finished.
Follow one signal through its leaves.
Start with one atomic root, then place two atomic states beneath it and move between them.
1 · Atomic root
Atomic states are leaves.
Signal is both the root and the active leaf. It contains no child states, so the topology ends here.
import { State, Statechart } from "@motive/motive";
class Signal extends State.Atomic<Signal>()("Signal") {}
export const SignalChart = Statechart.make(Signal);
2 · Atomic children
Topology ends at its atomic states.
Signal becomes compound while Stop and Proceed remain atomic. Entering Signal enters Stop, its first child by default.
import { State, Statechart, States } from "@motive/motive";
class Signal extends State.Compound<Signal>()("Signal") {
static states = States.make(() => [Stop, Proceed]);
}
class Stop extends State.Atomic<Stop>()("Stop") {}
class Proceed extends State.Atomic<Proceed>()("Proceed") {}
export const SignalChart = Statechart.make(Signal);
3 · Outgoing behavior
A leaf can own a way out.
Stop handles ClearSignal and transitions to Proceed. A state needs no children to own behavior.
import { State, Statechart, States, Transitions } from "@motive/motive";
import { Schema } from "effect";
const ClearSignal = Schema.TaggedStruct("ClearSignal", {});
class Signal extends State.Compound<Signal>()("Signal") {
static states = States.make(() => [Stop, Proceed]);
}
class Stop extends State.Atomic<Stop>()("Stop") {
static transitions = Transitions.make(this, ({ on }) => [
on(ClearSignal, Proceed),
]);
}
class Proceed extends State.Atomic<Proceed>()("Proceed") {}
export const SignalChart = Statechart.make(Signal);
4 · Ordinary lifecycle
Atomic does not mean terminal.
Proceed handles StopSignal and returns to Stop. Either atomic state may be exited, re-entered, and revisited without completing Signal.
import { State, Statechart, States, Transitions } from "@motive/motive";
import { Schema } from "effect";
const ClearSignal = Schema.TaggedStruct("ClearSignal", {});
const StopSignal = Schema.TaggedStruct("StopSignal", {});
class Signal extends State.Compound<Signal>()("Signal") {
static states = States.make(() => [Stop, Proceed]);
}
class Stop extends State.Atomic<Stop>()("Stop") {
static transitions = Transitions.make(this, ({ on }) => [
on(ClearSignal, Proceed),
]);
}
class Proceed extends State.Atomic<Proceed>()("Proceed") {
static transitions = Transitions.make(this, ({ on }) => [
on(StopSignal, Stop),
]);
}
export const SignalChart = Statechart.make(Signal);
Leaf is a topological claim
An active configuration follows the topology from the chart root to an atomic leaf. In the signal
chart, Signal remains active while either Stop or Proceed supplies the most specific fact about
the current situation.
That path matters when you read snapshots and queries: ancestors describe the broader context, and the atomic state tells you where that branch ends. A parallel state can activate several branches, so a later chart may have more than one active atomic leaf at the same time.
Atomic is not terminal
An atomic state can handle events, trigger transitions, run entry and exit actions, and be revisited as often as the model permits. It does not finish its parent merely by becoming active.
Use State.Done or State.Error when entering a state must report a terminal outcome. Those states
have completion semantics; State.Atomic has only leaf semantics.
Read the active leaf
- Start the signal and identify every state in its active configuration.
- Send
ClearSignal. Notice thatSignalremains active while its leaf changes fromStoptoProceed. - Send
StopSignal, then repeat the cycle. Nothing about either leaf limits how many times it can be entered.