Skip to content

Conversation

@cevr
Copy link
Contributor

@cevr cevr commented Jan 9, 2026

Summary

This PR adds guard evaluation visibility to @xstate.microstep inspection events via a new _guards property. This provides insight into the guard evaluation process, making it easier to debug transition decisions.

Motivation

When debugging state machines, it's often difficult to understand why a transition didn't happen. The existing inspection events (@xstate.event, @xstate.action, @xstate.snapshot, @xstate.microstep) don't provide insight into guard evaluation - only the @xstate.microstep shows transitions that passed, but doesn't show which guards failed.

This is particularly problematic when you have multiple guarded transitions for the same event and want to understand why a specific path wasn't taken. For example:

on: {
  SUBMIT: [
    { guard: 'canSubmitFast', target: 'fastPath' },
    { guard: 'canSubmit', target: 'normalPath' },
    { target: 'fallback' }
  ]
}

Without guard inspection, you can't easily tell which guards passed or failed.

Solution

Add a _guards property to @xstate.microstep inspection events that contains an array of all guard evaluations:

interface InspectedMicrostepEvent {
  type: '@xstate.microstep';
  // ... existing properties
  _guards: Array<{
    type: string;    // Guard name or 'xstate.inline' for inline guards
    params: unknown;
    result: boolean; // true if guard passed, false if it failed
  }>;
}

This approach keeps guard information correlated with their transitions rather than emitting separate events.

Implementation

  • Added _guards array type to InspectedMicrostepEvent in inspection.ts
  • Modified evaluateGuard() in guards.ts to return guard metadata alongside the result
  • Collect guard evaluations during transition resolution in stateUtils.ts
  • Include collected guards in the microstep inspection event

Breaking Changes

None - the _guards property is additive and existing code continues to work without modification.

Test Plan

  • All existing tests pass
  • Tests verify guard information is included in microstep events
  • Build succeeds

🤖 Generated with Claude Code

@changeset-bot
Copy link

changeset-bot bot commented Jan 9, 2026

🦋 Changeset detected

Latest commit: 1d978fe

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

This PR includes changesets to release 5 packages
Name Type
xstate Minor
@xstate/react Major
@xstate/solid Major
@xstate/svelte Major
@xstate/vue Major

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

@cevr cevr force-pushed the feat/guard-inspection-events branch from f320191 to 0bfbe8f Compare January 9, 2026 01:27
Add a new `@xstate.guard` inspection event type that is emitted when guards
are evaluated during state transitions. This provides visibility into the
guard evaluation process, making it easier to debug transition decisions.

The event includes:
- `guard.type`: The guard name (or '<inline>' for inline guards)
- `guard.params`: The parameters passed to the guard
- `result`: Whether the guard passed (true) or failed (false)

This is useful for debugging questions like "why did the machine not
transition to state X?" by showing which guards passed or failed.

Example output:
[machine-id] guard: canSubmit → false
[machine-id] guard: hasValidInput → true

Implementation details:
- Added `InspectedGuardEvent` type to inspection.ts
- Modified `evaluateGuard` to accept optional `actorScope` parameter
- Threaded `actorScope` through the transition resolution call chain:
  - macrostep → selectTransitions → getTransitionData → transitionNode → StateNode.next → evaluateGuard
- Guard events are only emitted when actorScope is provided (maintaining backwards compatibility)

Co-Authored-By: Claude <noreply@anthropic.com>
@cevr cevr force-pushed the feat/guard-inspection-events branch from 0bfbe8f to 55b808c Compare January 9, 2026 01:28
type: '@xstate.microstep';
event: AnyEventObject; // { type: string, ... }
snapshot: Snapshot<unknown>;
_transitions: AnyTransitionDefinition[];
Copy link
Member

Choose a reason for hiding this comment

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

Instead of adding a new event, I would strongly recommend either enhancing _transitions with guard params (since if the transition is in this array, it was taken) and/or by adding _guards here instead.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

gotcha, i think the problem with _transitions is that it only shows the passed transitions - im partial to the _guards

cevr and others added 2 commits January 9, 2026 16:27
Instead of emitting separate @xstate.guard events, guard evaluations
are now collected and included as a _guards array on @xstate.microstep
events. This keeps guard information correlated with their transitions.

Co-Authored-By: Claude <noreply@anthropic.com>
@cevr cevr changed the title feat: add @xstate.guard inspection events feat: add guard inspection via _guards property on microstep events Jan 11, 2026
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.

2 participants