Guide

Shallow History

Restore the previously active direct child without restoring its descendants.

Ordinary entry starts from a state's authored initial path. State.ShallowHistory gives a compound state another entry route: restore the direct child that was active when the state last exited.

Remember the branch, not the leaf.

Leaving Workspace normally ends its active configuration. Shallow history records which direct child was active and uses that branch the next time you enter through it.

1 · Ordinary entry

Reentry forgets where you were.

Pause from Editing.Body, then return to Workspace itself. Ordinary entry follows the authored initial path, so the editor returns to Browsing and forgets both levels of prior geometry.

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

const BeginEditing = Schema.TaggedStruct("BeginEditing", {});
const EditBody = Schema.TaggedStruct("EditBody", {});
const Pause = Schema.TaggedStruct("Pause", {});
const Resume = Schema.TaggedStruct("Resume", {});

export class Editor extends State.Compound<Editor>()("Editor") {
  static states = States.make(() => [Workspace, Paused]);
}

class Workspace extends State.Compound<Workspace>()("Workspace") {
  static states = States.make(() => [Browsing, Editing]);
  static transitions = Transitions.make(this, ({ on }) => [on(Pause, Paused)]);
}

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

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

class Title extends State.Atomic<Title>()("Title") {
  static transitions = Transitions.make(this, ({ on }) => [on(EditBody, Body)]);
}

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

class Paused extends State.Atomic<Paused>()("Paused") {
  static transitions = Transitions.make(this, ({ on }) => [
    on(Resume, Workspace),
  ]);
}

export const EditorChart = Statechart.make(Editor);

2 · One level of history

State.ShallowHistory restores the branch.

Recent remembers that Editing was active, then enters Editing through its ordinary initial path. The editor returns to Title, not the deeper Body leaf.

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

const BeginEditing = Schema.TaggedStruct("BeginEditing", {});
const EditBody = Schema.TaggedStruct("EditBody", {});
const Pause = Schema.TaggedStruct("Pause", {});
const Resume = Schema.TaggedStruct("Resume", {});

export class Editor extends State.Compound<Editor>()("Editor") {
  static states = States.make(() => [Workspace, Paused]);
}

class Workspace extends State.Compound<Workspace>()("Workspace") {
  static states = States.make(() => [Browsing, Editing, Recent]);
  static transitions = Transitions.make(this, ({ on }) => [on(Pause, Paused)]);
}

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

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

class Title extends State.Atomic<Title>()("Title") {
  static transitions = Transitions.make(this, ({ on }) => [on(EditBody, Body)]);
}

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

class Recent extends State.ShallowHistory()("Recent") {
  static readonly default = HistoryDefault.make(this, () => Browsing);
}

class Paused extends State.Atomic<Paused>()("Paused") {
  static transitions = Transitions.make(this, ({ on }) => [on(Resume, Recent)]);
}

export const EditorChart = Statechart.make(Editor);

Reentry has no memory

The first frame leaves Workspace from Editing.Body, then targets Workspace itself on resume. That is an ordinary entry, so Workspace follows its initial path back to Browsing. Returning to the same compound state does not imply returning to its former child.

Leaving also ends the old active lifetime. A later ordinary entry constructs fresh facets and remounts owned work just like any other entry; a matching state name does not continue the previous incarnation.

Shallow history remembers the direct child

The second frame adds Recent as a State.ShallowHistory child of Workspace. History is a route, not a phase: Recent never appears in the active configuration and owns no live work.

When Workspace exits, shallow history records its active direct child, Editing. Resume restores that child, then follows Editing's ordinary initial path to Title. It remembers which section was open, but not the nested position inside that section.

The restored configuration is therefore Editor.Workspace.Editing.Title, not the previously active Editor.Workspace.Editing.Body.

The default covers the absence of history

HistoryDefault.make is required because Recent may be targeted before Workspace has ever exited. With no record to restore, the default routes to Browsing.

Once a record exists, the recorded direct child wins and the default is not consulted. The default does not initialize or reset the history store; it makes the no-history case explicit and total.

Test the route

  1. Target Recent before Workspace has exited and confirm that the default selects Browsing.
  2. Change the initial child of Editing and predict where shallow restoration enters it.
  3. Put state-owned data on Editing and Body, then observe which facet depth is restored.