E-COMPONENT-ENGINE-SCOPE
A component declaration body contains an
<engine>
element. Engines are singletons.
What it means
Per PRIMER §7 + SPEC §51.0.K Machine Cohesion, engines are singleton-by-design. One declaration site → one running singleton instance for that scope. Components are the multi-instance vehicle — each render of a component spins up a fresh instance with its own internal state.
Engines and components are STRUCTURALLY DISTINCT. Declaring
an
<engine>
inside a component body would mean each component instance
spawns its own "singleton" — a contradiction in terms.
The compiler rejects at compile time.
Minimal reproducer
type Phase:enum = { Idle, Loading, Done }
component LoadCard {
<engine for=Phase initial=.Idle>
//^^^^^^
//E-COMPONENT-ENGINE-SCOPE: engine inside component body.
<Idle rule=.Loading />
<Loading rule=.Done />
<Done rule=.Idle />
</>
}
How to fix
-
Use plain reactive cells inside the component.
If the state is per-instance, declare it as ordinary
reactive cells:
<phase>: Phase = .Idle. No exhaustiveness check (engine's Tier 2 commitment is per-app, not per-component), but per-instance state works. -
Move the engine outside the component.
If the state is genuinely app-singleton (e.g., a global
router phase, a session-lifecycle state), declare the
engine at file scope OR in a parent
<program>body. Components read the engine variable via the canonical access path. -
Use a derived engine for per-instance projection.
If you want engine-like exhaustive case-analysis but
per-instance value, the
<engine for=Type derived=expr>form (§51.0.J) projects an enum from existing data without owning the state. Derived engines are still singletons but their value tracks an upstream cell. NOTE: derived engines also can't be declared inside components (same E-COMPONENT-ENGINE-SCOPE applies).
Why this is structural, not stylistic
The component-vs-engine distinction is one of scrml's architectural moves (Move 20 in the v0.next surface). Per §15.13.5 + §51.0.K, the two abstractions answer different questions:
- Component: "What's the UI shape of this reusable piece, parameterized by props?" Multi-instance; each render owns its state.
- Engine: "What's the canonical state machine governing this scope?" Singleton; one declaration = one running instance.
Conflating them (engine inside component) breaks the engine singleton invariant. The compiler enforces the distinction structurally so the architectural decomposition holds at scale — the moment you can have N engines from one declaration, "engine" loses its semantic core.
Nested engines: legal in OTHER engines, NOT in components
Per §51.0.K Machine Cohesion (sharpened S67), engines MAY be declared inside another engine's composite state-child body — per §51.0.Q.1. Outer × 1 = 1 inner instance, so the singleton invariant is preserved. Engines MAY NOT be declared in:
- Component bodies (this error)
- Function bodies
- Snippet bodies
Multi-call surfaces (function / snippet / component) cannot preserve singleton-ness, hence the structural rejection.
Related
- <engine> — element reference. Singleton lifecycle + declaration scope rules.
- E-IDLE-MISPLACED — sibling error class (structural-element placement).
- E-STRUCTURAL-ELEMENT-MISPLACED — generic structural-element-misuse error.
Specification
Normative text: SPEC §51.0.K (Machine Cohesion — singleton invariant; declaration-scope rules), §15.13.5 (components-vs-engines distinction — Move 20, D4 ratification), §51.0.Q.1 (nested engines in composite state-children — the ONE legal nesting form), §34 catalog row. Spec lives at compiler/SPEC.md .