Skip to content

fix(core): parallel top-level transitions preserve sibling region sub-state - #5564

Open
JSap0914 wants to merge 1 commit into
statelyai:mainfrom
JSap0914:fix/parallel-state-resets-siblings-to-initial
Open

fix(core): parallel top-level transitions preserve sibling region sub-state#5564
JSap0914 wants to merge 1 commit into
statelyai:mainfrom
JSap0914:fix/parallel-state-resets-siblings-to-initial

Conversation

@JSap0914

Copy link
Copy Markdown
Contributor

Summary

Fixes #5214

Bug

When a transition is defined directly on a parallel state (its on property) and targets a specific sub-region, all other parallel regions were being reset to their initial sub-states.

const machine = createMachine({
  id: 'parallelExample',
  type: 'parallel',
  on: {
    ARCHIVE: { target: '#parallelExample.phase.archive' },
    EDIT: { target: '#parallelExample.mode.edit' },
  },
  states: {
    phase: { initial: 'inquiry', states: { inquiry: {}, archive: {} } },
    mode: { initial: 'new', states: { new: {}, edit: {} } },
  },
});

const actor = createActor(machine).start();
actor.send({ type: 'EDIT' });
// { phase: 'inquiry', mode: 'edit' }

actor.send({ type: 'ARCHIVE' });
// Before fix: { phase: 'archive', mode: 'new' }  ← mode wrongly reset
// After fix:  { phase: 'archive', mode: 'edit' } ← mode preserved ✓

Root Cause

In computeExitSet, when the transition domain is a parallel state, ALL active descendants of that domain were added to the exit set — including states in unrelated sibling regions.

In computeEntrySet, the domain (parallel state) was pushed into the ancestor list, which caused addAncestorStatesToEnter to call addDescendantStatesToEnter for every child of the parallel state not already targeted, resetting them to their initial sub-states.

Fix

When the transition's source IS the parallel domain and no target is the domain itself:

  1. computeExitSet: only adds states in the parallel regions that contain the transition targets to the exit set. Sibling regions are left untouched.
  2. computeEntrySet: skips pushing the parallel domain into ancestors, so addAncestorStatesToEnter never resets sibling regions.

Cross-region transitions (source is a child state, not the domain) and full-reset transitions that target the domain node itself (e.g. RESET: '#machineId') continue to work with the original behaviour.

Verification

pnpm --filter xstate test
# Test Files  73 passed (73)
# Tests       1728 passed | 13 skipped | 1 todo (1742)

New regression test: "transitions defined on a parallel state should not reset sibling regions to their initial sub-state" in packages/core/test/parallel.test.ts.

AI-assisted contribution.

When a transition is defined directly on a parallel state (in its `on`
property) and targets a specific sub-region, the other parallel regions
were incorrectly reset to their initial sub-states.

Root cause: `computeExitSet` was exiting ALL descendants of the
parallel domain, and `computeEntrySet` was pushing the domain into the
ancestor list which caused `addAncestorStatesToEnter` to add every
sibling region via `addDescendantStatesToEnter` (starting from initial).

Fix: when the transition's source IS the parallel domain and no target is
the domain itself, scope the exit set to only the regions that contain
the targets, and skip pushing the domain into ancestors so sibling
regions are not re-entered from scratch.

Cross-region transitions (source is a child state, not the domain) and
full-reset transitions that target the domain node itself continue to
work as before.

Fixes statelyai#5214
Copilot AI review requested due to automatic review settings June 27, 2026 05:02
@changeset-bot

changeset-bot Bot commented Jun 27, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: c0970e2

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
xstate Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Bug: defining transitions top-level in a parallel state tree, transitions other childs back to initial state

2 participants