<onTransition>
Effect on transition. Runs when an engine variable moves from one variant to another. Filterable by from / to / if / once.
Syntax
<onTransition [from=.Variant] [to=.Variant] [once] [if=cond]>
<!-- effect body -->
</onTransition>
All filter attributes are optional individually, but at
least one of
from=
or
to=
is required — without a target,
E-ONTRANSITION-NO-TARGET
fires.
Worked example
A loading flow with analytics on the success transition and cleanup on the error transition.
type Phase:enum = { Idle, Loading, Error(msg: string), Success(count: int) }
<engine for=Phase initial=.Idle>
<Idle rule=.Loading />
<Loading rule=(.Success | .Error) />
<Error rule=.Loading />
<Success rule=.Idle />
<onTransition from=.Loading to=.Success>
${ analytics.track("load.success") }
</onTransition>
<onTransition to=.Error>
${ cleanup() }
</onTransition>
<onTransition from=.Idle to=.Loading once>
${ firstLoadInstrumentation() }
</onTransition>
</engine>
Three handlers, three filter shapes: from-and-to (specific edge), to-only (any incoming Error), and from-and-to-once (first occurrence only).
Semantics
- Fires after the transition commits. The engine variable has already taken the new variant when the handler runs. Reads of the engine var inside the body see the destination state.
-
Lives inside an engine.
<onTransition>is a child of<engine>, not free-standing. It is also forbidden inside<match>blocks — E-MATCH-ONTRANSITION-FORBIDDEN. Match is pass-through case analysis; effects belong on engines. -
from=/to=are filters. Omitting one means the handler fires for any source / destination of the other. Omitting both is the E-ONTRANSITION-NO-TARGET error. -
onceis a bare attribute. Marks the handler to fire only on the first matching transition. The compiler tracks a per-handler "has- fired" flag. -
if=is a runtime guard. Expression evaluated at transition time; handler fires only when truthy. Composes with from / to / once. -
Multiple handlers per engine are allowed.
The engine can have any number of
<onTransition>children with overlapping filters. All matching handlers fire in source order.
Errors this feature can fire
- E-ONTRANSITION-NO-TARGET — neither from= nor to=
- E-MATCH-ONTRANSITION-FORBIDDEN — inside <match> block
- E-STRUCTURAL-ELEMENT-MISPLACED — outside <engine>
Edge cases
-
Self-writes don't trigger the handler.
Idempotent self-writes (writing the current variant)
are runtime no-ops per
W-ENGINE-SELF-WRITE-DETECTED; no transition occurs so no handler fires. -
internal:rule= transitions skip the handler.
Inside a composite state-child with
internal:rule=, the inner-engine transition preserves the outer state — the outer engine's<onTransition>does NOT fire. Per SPEC §51.0.O. -
effect=on a state-child is the per-rule alternative. For single-target rules, an inlineeffect=attribute is the shorter form. Multi-target rules fire E-ENGINE-EFFECT-AMBIGUOUS because the destination is ambiguous; use<onTransition>instead. -
Once + if= compose.
once if=condfires only on the FIRST transition where cond is truthy. Subsequent matching transitions are skipped whether cond is truthy or not.
Related features
- <engine> — the container. Without an engine, onTransition has nowhere to live.
- effect= attribute on a state-child — per-rule inline form. Single-target only. Reference page queued.
- <onTimeout> / <onIdle> — sibling temporal triggers (per-state and engine-wide watchdog respectively). Reference pages queued.
- <match> — forbidden context. Promote to engine first if you need transition effects.
Availability
| Surface | Since | Notes |
|---|---|---|
| <onTransition> from / to / once / if | S57 D2.8 | SPEC §51.0.H; engine-only structural element |
| E-ONTRANSITION-NO-TARGET | S74 A1b B17.3 | Caught neither-from-nor-to misuse |
| internal:rule= excludes onTransition | S67 / S68 §51.0.O | Internal transitions preserve outer-engine lifecycle including handler skip |
Specification
This page summarizes SPEC §51.0.H (effect= / onTransition) and §51.0.O (internal:rule= prefix). The normative text lives at compiler/SPEC.md in the scrmlTS repository.