# `Plushie.Runtime`
[🔗](https://github.com/plushie-ui/plushie-elixir/blob/v0.7.2/lib/plushie/runtime.ex#L1)

Core lifecycle GenServer for Plushie applications.

The runtime is the heartbeat of a Plushie app. It owns the Elm-style
update loop: event in -> model out -> view out -> snapshot to bridge.

## Startup

On `init/1` the runtime:
  1. Calls `app.init(app_opts)` to get the initial model (and optional commands).
  2. Calls `app.view(model)` to produce the initial UI tree.
  3. Normalizes the tree via `Plushie.Tree.normalize/1`.
  4. Sends a full snapshot to the bridge via `Plushie.Bridge.send_snapshot/2`.
  5. Executes any commands returned from `init/1`.

## Event loop

On every `{:renderer_event, event}`:
  1. Calls `app.update(model, event)`.
  2. Executes returned commands.
  3. Calls `app.view(model)` on the new model.
  4. Diffs against the previous tree; sends a patch if changed, or a
     full snapshot on first render / after renderer restart.

## State shape

    %{
      app:                module(),
      model:              term(),
      bridge:             pid() | atom(),
      daemon:             boolean(),
      tree:               map() | nil,
      subscriptions:      %{term() => {:timer, reference()} | {:renderer, atom(), non_neg_integer() | nil}},
      subscription_keys:  [term()],
      windows:            MapSet.t(),
      async_tasks:        %{atom() => {pid(), reference()}},
      pending_effects:    %{String.t() => %{tag: atom(), kind: String.t(), timer_ref: reference()}},
      pending_timers:     %{term() => {reference(), integer()}},
      pending_coalesce:   %{term() => Plushie.Event.t()},
      pending_coalesce_order: [term()],
      coalesce_timer:     reference() | nil,
      consecutive_errors: non_neg_integer(),
      pending_interact:   {GenServer.from(), String.t(), reference(), reference(), String.t(), map()} | nil
    }

## Exit trapping

The runtime traps exits so a bridge crash does not silently kill it.

# `await_async`

```elixir
@spec await_async(GenServer.server(), atom(), timeout()) ::
  :ok | {:error, :await_in_progress}
```

Waits for an async task with the given tag to complete.

If the task has already completed, returns immediately. Otherwise
blocks until the task finishes and its result has been processed
through update/2.

Returns `{:error, :await_in_progress}` if another caller is already
waiting for the same tag.

# `child_spec`

Returns a specification to start this module under a supervisor.

See `Supervisor`.

# `dispatch`

```elixir
@spec dispatch(GenServer.server(), term()) :: :ok
```

Dispatches a message through `app.update/2`, then re-renders.

Fire-and-forget from the caller's perspective. The runtime processes
the message asynchronously. Use this to send results from spawned
processes back to the runtime:

    runtime = self()  # inside update/2, self() is the runtime
    spawn(fn ->
      result = expensive_computation()
      Plushie.Runtime.dispatch(runtime, {:computation_done, result})
    end)

    # In update/2:
    def update(model, {:computation_done, result}), do: ...

Prefer `Plushie.Command.task/2` for most async work. Use `dispatch/2`
when you need direct control over the spawned process lifecycle.

# `find_node`

```elixir
@spec find_node(GenServer.server(), String.t()) :: map() | nil
```

Finds a node in the current tree by exact scoped ID.

# `find_node`

```elixir
@spec find_node(GenServer.server(), String.t(), String.t()) :: map() | nil
```

Finds a node in the current tree by exact scoped ID inside a specific window.

# `find_node_by`

```elixir
@spec find_node_by(GenServer.server(), (map() -&gt; boolean())) :: map() | nil
```

Finds a node in the current tree using a predicate function.

# `get_bridge`

```elixir
@spec get_bridge(GenServer.server()) :: pid() | atom() | nil
```

Returns the bridge pid for this runtime.

# `get_focused`

```elixir
@spec get_focused(GenServer.server()) :: String.t() | nil
```

Returns the ID of the currently focused widget, or `nil`.

Focus is tracked automatically from renderer status events.

# `get_health`

```elixir
@spec get_health(GenServer.server()) :: %{
  status: :healthy | :degraded,
  consecutive_errors: non_neg_integer(),
  consecutive_view_errors: non_neg_integer()
}
```

Returns the runtime's current health status.

The returned map contains:

  * `:status` - `:healthy` or `:degraded`
  * `:consecutive_errors` - update/2 error count since last success
  * `:consecutive_view_errors` - view/1 error count since last success

A runtime is `:degraded` when consecutive view errors have
accumulated (the UI is showing stale content). Otherwise it
is `:healthy`.

# `get_model`

```elixir
@spec get_model(GenServer.server()) :: term()
```

Returns the current app model synchronously.

# `get_tree`

```elixir
@spec get_tree(GenServer.server()) :: map() | nil
```

Returns the current normalized UI tree synchronously.

# `interact`

```elixir
@spec interact(GenServer.server(), String.t(), map(), map(), timeout()) ::
  :ok
  | {:error,
     :interact_in_progress
     | :renderer_restarted
     | {:renderer_exit, term()}
     | {:timeout, String.t(), map()}}
```

Performs a synchronous interact via the renderer.

Sends an interact request (e.g. click, type_text) to the renderer, which
processes it against its widget tree and sends back events. The runtime
processes those events through `update/2` and re-renders. Blocks until
the renderer signals completion. Returns an error if another interact is
already in flight, or if the renderer exits or restarts before the
interaction finishes.

# `register_effect_stub`

```elixir
@spec register_effect_stub(
  GenServer.server(),
  Plushie.Effect.kind(),
  term(),
  timeout()
) ::
  :ok | {:error, :stub_ack_pending | :renderer_restarted}
```

Registers an effect stub with the renderer.

The renderer will return `response` immediately for any effect of
the given `kind`, without executing the real effect. Blocks until
the renderer confirms the stub is stored.

The `kind` matches the effect function name as an atom (e.g.
`:file_open`, `:clipboard_write`).

Returns `{:error, :stub_ack_pending}` if a register or unregister
for the same kind is already awaiting confirmation. Returns
`{:error, :renderer_restarted}` if the renderer restarts while the
registration is in flight (the stub must be re-registered).

# `start_link`

```elixir
@spec start_link(keyword()) :: GenServer.on_start()
```

Starts the runtime linked to the calling process.

Required opts:
  - `:app`    - module implementing `Plushie.App`
  - `:bridge` - pid or registered name of the `Plushie.Bridge` GenServer

Optional opts:
  - `:name` - registration name passed to `GenServer.start_link/3`

Any other opts are forwarded to `app.init/1` as the app opts keyword list.

# `sync`

```elixir
@spec sync(runtime :: GenServer.server()) :: :ok | {:ok, :view_error}
```

Waits for the runtime to finish processing all pending messages.

Returns `:ok` once the runtime is idle, or `{:ok, :view_error}` if
the most recent view/1 call failed. In the view error case, the model
has been updated but the tree is stale (showing the last successful
render). Use this to synchronize after dispatching events or starting
the runtime, ensuring init/update cycles have completed before
inspecting state.

# `unregister_effect_stub`

```elixir
@spec unregister_effect_stub(GenServer.server(), Plushie.Effect.kind(), timeout()) ::
  :ok | {:error, :stub_ack_pending | :renderer_restarted}
```

Removes a previously registered effect stub.

Blocks until the renderer confirms the stub is removed.

Returns `{:error, :stub_ack_pending}` if a register or unregister
for the same kind is already awaiting confirmation. Returns
`{:error, :renderer_restarted}` if the renderer restarts while the
request is in flight.

---

*Consult [api-reference.md](api-reference.md) for complete listing*
