Deep vs Shallow History

Deep vs Shallow History

Master SCXML history states by building a text editor with suspend/resume functionality. This example demonstrates the critical difference between deep history (restores entire nested state hierarchy) and shallow history (restores only immediate child state).

What you'll learn:

  • History state fundamentals: <history type="deep"> vs <history type="shallow">
  • Nested state hierarchies and how history interacts with them
  • Real-world suspend/resume patterns for applications
  • When to choose deep vs shallow history

Try It

Click Start, navigate through the editor states, then suspend and resume with either deep or shallow history:

How to explore:

  1. Click start to enter the editor
  2. Navigate: settingsclose → back to document view
  3. In editing mode: escape (command) → v (visual) → escape (command) → i (insert)
  4. Click suspend to pause the application
  5. Compare: resume_deep vs resume_shallow

The Core Concept

When a compound state is exited, history states remember which child states were active. The question is: how much do they remember?

┌─────────────────────────── editor ───────────────────────────┐
│                                                               │
│  ┌─────────────── document ───────────────┐                  │
│  │                                         │                  │
│  │  ┌───────── editing ─────────┐         │                  │
│  │  │                           │         │                  │
│  │  │  [insert] [command] [visual]        │     [preview]    │
│  │  │     ▲                     │         │                  │
│  │  │     │ Currently active    │         │                  │
│  │  └─────┼─────────────────────┘         │                  │
│  │        │                               │                  │
│  └────────┼───────────────────────────────┘                  │
│           │                                                   │
│    [H*]───┘  Deep history: remembers [document, editing, insert] │
│    [H]────── Shallow history: remembers only [document]       │
│                                                               │
└───────────────────────────────────────────────────────────────┘

Deep History (type="deep")

Captures the entire nested state configuration:

  • Active: [editor, document, editing, insert]
  • After suspend → resume_deep: Returns to [editor, document, editing, insert]
  • Use case: Full application restore (like reopening a suspended app exactly where you left off)

Shallow History (type="shallow")

Captures only the immediate child of the compound state:

  • Active: [editor, document, editing, insert]
  • After suspend → resume_shallow: Returns to [editor, document] → then document's initial[editor, document, editing, insert]
  • Wait—same result? Not always! See the key difference below.

The Key Difference: Settings Detour

The difference becomes clear when you take a detour through settings:

Scenario: Editing in Visual Mode, then Settings

  1. Start in: [editor, document, editing, visual]
  2. Send settings event → Go to: [editor, settings]
  3. Send suspend event → Exit editor
  4. Now try both resumes:
History Type What's Restored Result
Deep Full previous config [editor, document, editing, visual]
Shallow Only settings (last direct child) [editor, settings]

Deep history remembered you were in visual mode inside editing inside document. Shallow history only remembered you were in settings.


The SCXML Implementation

xml
<?xml version="1.0" encoding="UTF-8"?>
<scxml xmlns="http://www.w3.org/2005/07/scxml" version="1.0"
       datamodel="ecmascript" initial="idle" name="DeepHistoryDemo">

  <datamodel>
    <data id="suspendCount" expr="0"/>
  </datamodel>

  <state id="idle">
    <transition event="start" target="editor"/>
  </state>

  <state id="editor" initial="document">
    <!-- Both history types defined -->
    <history id="editor_deep_history" type="deep">
      <transition target="document"/>  <!-- Default if no history -->
    </history>

    <history id="editor_shallow_history" type="shallow">
      <transition target="document"/>
    </history>

    <transition event="suspend" target="suspended">
      <assign location="suspendCount" expr="suspendCount + 1"/>
    </transition>

    <transition event="settings" target="settings"/>

    <!-- Nested document state with further nesting -->
    <state id="document" initial="editing">
      <state id="editing" initial="insert">
        <state id="insert">
          <transition event="escape" target="command"/>
        </state>
        <state id="command">
          <transition event="i" target="insert"/>
          <transition event="v" target="visual"/>
        </state>
        <state id="visual">
          <transition event="escape" target="command"/>
        </state>
      </state>
      <transition event="preview" target="preview"/>
    </state>

    <state id="preview">
      <transition event="edit" target="document"/>
    </state>

    <state id="settings">
      <transition event="close" target="document"/>
    </state>
  </state>

  <state id="suspended">
    <!-- Resume with deep history - full state restored -->
    <transition event="resume_deep" target="editor_deep_history"/>

    <!-- Resume with shallow history - only direct child restored -->
    <transition event="resume_shallow" target="editor_shallow_history"/>

    <transition event="quit" target="idle"/>
  </state>
</scxml>

Key Implementation Details

Defining History States:

xml
<history id="editor_deep_history" type="deep">
  <transition target="document"/>  <!-- Default target if no history exists yet -->
</history>

The nested <transition> provides a default target for the first entry (before any history has been recorded).

Transitioning to History:

xml
<transition event="resume_deep" target="editor_deep_history"/>

Targeting a history pseudo-state causes the interpreter to restore the remembered configuration instead of entering a normal state.


Real-World Applications

Application Suspend/Resume

xml
<state id="app" initial="main_menu">
  <history id="app_history" type="deep"/>

  <transition event="os.suspend" target="suspended">
    <script>saveAppState();</script>
  </transition>

  <!-- Deep nested UI states -->
  <state id="main_menu">...</state>
  <state id="game">
    <state id="playing">
      <state id="level1"/>
      <state id="level2"/>
    </state>
    <state id="paused"/>
  </state>
</state>

<state id="suspended">
  <transition event="os.resume" target="app_history"/>
</state>

Wizard with Back Button

xml
<state id="wizard" initial="step1">
  <history id="wizard_history" type="shallow"/>

  <state id="step1">
    <state id="step1_basic"/>
    <state id="step1_advanced"/>
    <transition event="next" target="step2"/>
  </state>

  <state id="step2">
    <transition event="back" target="wizard_history"/>  <!-- Returns to step1 -->
    <transition event="next" target="step3"/>
  </state>
</state>

Here, shallow history makes sense—going "back" should return to step1, not necessarily to step1_advanced.


Decision Guide: Deep vs Shallow

Scenario Recommended Reason
App suspend/resume Deep Users expect exact state restoration
Wizard back button Shallow Return to step, not sub-state of step
Tab switching Shallow Remember which tab, not scroll position
Undo/redo system Deep Precise state restoration
Error recovery Deep Resume exactly where operation failed
Menu navigation Shallow Remember section, start fresh in it

Code Generation

Java

bash
scxml-gen deep-history.scxml -t java -o DeepHistoryDemo.java --package com.example
java
DeepHistoryDemo sm = new DeepHistoryDemo();
sm.start();

// Navigate deep into the hierarchy
sm.send("start");       // → editor.document.editing.insert
sm.send("escape");      // → editor.document.editing.command
sm.send("v");           // → editor.document.editing.visual

// Suspend
sm.send("suspend");     // → suspended

// Deep resume restores visual mode
sm.send("resume_deep"); // → editor.document.editing.visual ✓

// Or shallow resume would go to document's initial
sm.send("resume_shallow"); // → editor.document.editing.insert

JavaScript

javascript
import { DeepHistoryDemo } from './DeepHistoryDemo.js';

const sm = new DeepHistoryDemo();
sm.onStateChange(() => {
    console.log('Active:', [...sm.getActiveStateIds()]);
});

sm.start();
sm.send('start');
sm.send('escape');  // insert → command
sm.send('v');       // command → visual
sm.send('suspend');
sm.send('resume_deep');  // Back to visual!

C

bash
scxml-gen deep-history.scxml -t c -o deep_history.c
c
DeepHistoryDemo sm;
DeepHistoryDemo_init(&sm);
DeepHistoryDemo_start(&sm);

DeepHistoryDemo_send(&sm, EVT_START, NULL);
DeepHistoryDemo_send(&sm, EVT_ESCAPE, NULL);
DeepHistoryDemo_send(&sm, EVT_V, NULL);
DeepHistoryDemo_send(&sm, EVT_SUSPEND, NULL);
DeepHistoryDemo_send(&sm, EVT_RESUME_DEEP, NULL);  // Full restore

Common Mistakes

1. Forgetting the Default Transition

xml
<!-- ❌ Wrong: No default if history is empty -->
<history id="my_history" type="deep"/>

<!-- ✅ Correct: Provide default target -->
<history id="my_history" type="deep">
  <transition target="default_child"/>
</history>

2. Using Deep When Shallow is Appropriate

xml
<!-- ❌ Probably wrong for a wizard -->
<state id="wizard">
  <history id="h" type="deep"/>  <!-- Too specific for back button -->
  ...
</state>

<!-- ✅ Better: User expects to restart the step -->
<state id="wizard">
  <history id="h" type="shallow"/>
  ...
</state>

3. Multiple History States with Same Type

You can have both types in the same parent—use them for different purposes:

xml
<state id="app">
  <!-- For full restore (os suspend/resume) -->
  <history id="app_full_history" type="deep"/>

  <!-- For partial restore (switching modes) -->
  <history id="app_section_history" type="shallow"/>
</state>

Summary

Aspect Deep History Shallow History
Remembers Entire nested hierarchy Only immediate child
Use case Full application restore Section navigation
Syntax type="deep" type="shallow"
Default shallow (if type omitted)

Files

File Description
deep-history.scxml SCXML source file
deep-history-player.html Interactive demo

Next Steps