scrml.dev v0.7.1
Reference › Errors

E-COMPONENT-ENGINE-SCOPE

A component declaration body contains an <engine> element. Engines are singletons.

Error Compile-time SPEC §51.0.K (normative)

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

  1. 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.
  2. 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.
  3. 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

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 .