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
- Change
ArchivedtoState.Doneand predict the instance status after publishing completes. - Remove the
State.done(this)transition and identify the final active configuration. - Rename
Completedwithout changing its state kind. Confirm that completion behavior does not change.