Guide

Atomic States

Read State.Atomic as a leaf-state claim, not a terminal-state claim.

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

  1. Start the signal and identify every state in its active configuration.
  2. Send ClearSignal. Notice that Signal remains active while its leaf changes from Stop to Proceed.
  3. Send StopSignal, then repeat the cycle. Nothing about either leaf limits how many times it can be entered.