scrml.dev v0.7.1
Reference › Errors

E-IDLE-MISPLACED

<onIdle> appears inside a state-child body. Engine-wide watchdog must sit at engine root.

Error Compile-time Added S77 (A5-6) SPEC §51.0.R (normative)

What it means

<onIdle> is the engine-wide event-timeout watchdog — armed at module-init, reset on every successful transition, fires after N ms of silence across the whole engine. Per SPEC §51.0.R, it MUST sit at engine-root scope (sibling of state-children), NOT inside any state-child body.

When the compiler finds an <onIdle> element nested inside a state-child body, E-IDLE-MISPLACED fires. The error message directs the developer to <onTimeout>, which IS the per-state alternative.

Minimal reproducer

type Phase:enum = { Active, Idle }

<engine for=Phase initial=.Active>
  <Active rule=.Idle>
    <onIdle after=5m to=.Idle/>
    //  ^^^^^^^^^^^^^^^^^^^^^^^^^^^
    //  E-IDLE-MISPLACED: <onIdle> inside a state-child body.
    //  Use <onTimeout> for per-state semantics, or
    //  move <onIdle> to engine root.
  </>
  <Idle/>
</>

How to fix

Two paths; pick the one that matches your intent:

Intent A: per-state timer (most common)

If you want the timer to apply only when the engine is in this specific state, you want <onTimeout> instead. Same syntax, different semantics: armed on entry, cleared on exit.

<Active rule=.Idle>
  <onTimeout after=5m to=.Idle/>
  // Per-state timer — armed when entering .Active, cleared on exit.
</>

Intent B: engine-wide watchdog

If you want a session-idle watchdog that resets on every transition (including transitions back to the same state), move <onIdle> to engine root:

<engine for=Phase initial=.Active>
  <Active rule=.Idle>
    <button onclick=refresh()>I'm here</button>
  </>
  <Idle/>
  <onIdle after=5m to=.Idle/>
  // Engine-wide — armed at module-init, reset on every transition.
</>

<onTimeout> vs <onIdle> decision

  • Use onTimeout when the timer is conceptually about this state: a loading state timing out, a confirmation modal auto-dismissing, a temporary success banner clearing.
  • Use onIdle when the timer is conceptually about the whole engine: session-idle detection, inactivity-based logout, screen-lock after no activity.
  • Both compose — an engine MAY declare both per-state onTimeout(s) AND a single onIdle watchdog. Each occupies a distinct slot in the runtime timer map.

Related

Specification

Normative text: SPEC §51.0.R (form + placement + engine-wide semantics) + §51.0.M (per-state <onTimeout> alternative) + §34 catalog row. Spec lives at compiler/SPEC.md .