Guide

Done

Finish a nested state or terminate the chart instance at the root.

State.Done is a terminal leaf. Entering it completes the state that owns it. That completion may end the chart, or it may become input to a larger topology.

Put completion at the right boundary.

State.Done marks one boundary complete. At the root it ends the chart instance; inside a compound state it gives that state a completion its parent can handle.

1 · Names describe; state kinds behave

Completed can still be an ordinary state.

The model enters a leaf named Completed. Because that leaf is State.Atomic, the report remains Running. Its name does not create completion behavior.

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

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

export class Report extends State.Compound<Report>()("Report") {
  static states = States.make(() => [Editing, Completed]);
}

class Editing extends State.Atomic<Editing>()("Editing") {
  static transitions = Transitions.make(this, ({ on }) => [
    on(Finish, Completed),
  ]);
}

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

export const ReportChart = Statechart.make(Report);

2 · Complete the root

A root-level State.Done ends the instance.

Completed is now State.Done. Because it belongs directly to Report, entering it completes the chart. The final configuration remains available in the Done snapshot.

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

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

export class Report extends State.Compound<Report>()("Report") {
  static states = States.make(() => [Editing, Completed]);
}

class Editing extends State.Atomic<Editing>()("Editing") {
  static transitions = Transitions.make(this, ({ on }) => [
    on(Finish, Completed),
  ]);
}

class Completed extends State.Done<Completed>()("Completed") {}

export const ReportChart = Statechart.make(Report);

3 · Complete a nested state

Nested completion is behavior the parent can handle.

Completed now finishes Publishing, not the whole report. State.done(this) lets Publishing react to its own completion and move the still-running chart to Archived.

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

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

export class Report extends State.Compound<Report>()("Report") {
  static states = States.make(() => [Publishing, Archived]);
}

class Publishing extends State.Compound<Publishing>()("Publishing") {
  static states = States.make(() => [Editing, Completed]);
  static transitions = Transitions.make(this, ({ on }) => [
    on(State.done(this), Archived),
  ]);
}

class Editing extends State.Atomic<Editing>()("Editing") {
  static transitions = Transitions.make(this, ({ on }) => [
    on(Finish, Completed),
  ]);
}

class Completed extends State.Done<Completed>()("Completed") {}

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

export const ReportChart = Statechart.make(Report);

Names do not complete states

The first frame reaches a leaf named Completed, but that leaf is an ordinary State.Atomic. The report stays there and its instance remains Running. State names describe the domain; state kinds define behavior.

That distinction lets names stay honest. A domain state may be called Complete, Closed, or Failed without silently acquiring terminal semantics.

Done at the root ends the instance

The second frame changes only Completed from State.Atomic to State.Done. Because Completed belongs directly to the root Report, entering it completes the chart instance. Its snapshot changes from Running to Done and preserves the final configuration for inspection.

A done instance accepts no more external events. State.Done is therefore a behavioral commitment, not a decorative end marker.

Done inside a compound state completes that state

The final frame introduces a compound state named Publishing. Its Completed child is done, so entering that child completes Publishing. The chart produces the corresponding State.done(Publishing) event—written inside Publishing.transitions as State.done(this).

Publishing handles its completion by moving the report to Archived. Because Archived is an ordinary atomic state, the root report remains Running. The nested boundary finished; the chart did not.

Test the boundary

  1. Change Archived to State.Done and predict the instance status after publishing completes.
  2. Remove the State.done(this) transition and identify the final active configuration.
  3. Rename Completed without changing its state kind. Confirm that completion behavior does not change.