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

Native desktop GUIs from Elixir, powered by iced.

## Quick start

    {:ok, pid} = Plushie.start_link(MyApp)

## Dev mode (live code reloading)

    # In config/dev.exs:
    config :plushie, code_reloader: true

    # Or as a start_link option:
    {:ok, pid} = Plushie.start_link(MyApp, code_reloader: true)

## Under a supervisor

    children = [
      {Plushie, app: MyApp}
    ]

## Options

- `:app`: (required) the app module implementing `Plushie.App`
- `:app_opts`: opts forwarded to `app.init/1` (default: `[]`)
- `:binary`: path to the plushie binary (default: auto-resolved)
- `:name`: supervisor registration name (default: `Plushie`)
- `:daemon`: if `true`, keep running after the last window closes
                    (default: `false`). In daemon mode, `all_windows_closed`
                    is delivered to `update/2` instead of triggering shutdown.
- `:code_reloader`: enable dev-mode live reloading. `false` (default),
                    `true`, or a keyword list of reloader options
                    (`:debounce_ms`, `:rebuild_artifacts`). Can also be set
                    via `config :plushie, code_reloader: true`.
- `:transport`: `:spawn` (default, spawns the renderer as a child
                    process), `:stdio` (reads/writes the BEAM's own
                    stdin/stdout, for use with `plushie --exec`), or
                    `{:iostream, pid}` (custom transport via iostream
                    adapter, see `Plushie.Bridge` for the protocol)
- `:format`: wire format, `:msgpack` (default) or `:json`
- `:log_level`: plushie binary log level (`:off`, `:error`, `:warning`, `:info`, `:debug`).
                    Default: `:error`.
- `:renderer_args`: extra CLI args passed to the renderer process
- `:heartbeat_interval`: maximum time (ms) between renderer messages
                    before the bridge considers it unresponsive and restarts
                    the renderer. `nil` disables the watchdog. Default: `30_000`.

When `:transport` is `:stdio` or `{:iostream, pid}`, the `:binary`
option is ignored (no renderer subprocess is spawned).

## Telemetry

Plushie emits `:telemetry` events for observability. Spans include
both `start` and `stop` (or `exception`) suffixes automatically.

### Spans

Spans are emitted via `:telemetry.span/3`. Each produces
`[:plushie, <name>, :start]` and `[:plushie, <name>, :stop]`
events (or `:exception` on failure).

- `[:plushie, :view]` - calls `app.view(model)`.
  Metadata: `%{app: module()}`.
- `[:plushie, :normalize]` - normalizes the raw view tree into
  canonical wire format, including widget rendering and memo caching.
  Metadata: `%{app: module()}`.
- `[:plushie, :diff]` - diffs old and new trees to produce patch
  operations. Metadata: `%{app: module()}`.
- `[:plushie, :update]` - calls `app.update(model, event)`.
  Metadata: `%{app: module(), event: Plushie.Event.t()}`.
- `[:plushie, :commands]` - executes commands returned by
  `update/2` or `init/1`. Metadata: `%{count: non_neg_integer()}`.
- `[:plushie, :subscriptions, :sync]` - diffs and synchronizes
  active subscriptions. Metadata: `%{}`.

### Single events

Single events are emitted via `:telemetry.execute/3`.

#### Runtime

- `[:plushie, :runtime, :view_error]` - `view/1` raised or threw.
  Measurements: `%{count: 1}`. Metadata: `%{app: module()}`.
- `[:plushie, :runtime, :update_error]` - `update/2` raised or threw.
  Measurements: `%{count: 1}`. Metadata: `%{app: module(), event: term()}`.
- `[:plushie, :runtime, :effect_timeout]` - a pending effect
  request timed out. Measurements: `%{count: 1}`.
  Metadata: `%{id: term()}`.
- `[:plushie, :runtime, :ticks_drained]` - coalesced multiple
  pending ticks into one cycle. Measurements: `%{count: integer()}`.
  Metadata: `%{tag: atom()}`.

#### Tree

- `[:plushie, :memo, :hit]` - memo cache hit during normalization.
  Measurements: `%{count: 1}`. Metadata: `%{id: String.t()}`.
- `[:plushie, :memo, :miss]` - memo cache miss during normalization.
  Measurements: `%{count: 1}`. Metadata: `%{id: String.t()}`.
- `[:plushie, :widget_cache, :hit]` - widget view cache hit.
  Measurements: `%{count: 1}`.
  Metadata: `%{id: String.t(), module: module()}`.
- `[:plushie, :widget_cache, :miss]` - widget view cache miss.
  Measurements: `%{count: 1}`.
  Metadata: `%{id: String.t(), module: module()}`.

#### Bridge

- `[:plushie, :bridge, :send]` - frame sent to the renderer.
  Measurements: `%{byte_size: non_neg_integer()}`.
- `[:plushie, :bridge, :receive]` - frame received from the renderer.
  Measurements: `%{byte_size: non_neg_integer()}`.
- `[:plushie, :bridge, :restart]` - renderer process restarted.
  Measurements: `%{count: pos_integer()}` (cumulative restart count).
- `[:plushie, :bridge, :protocol_error]` - failed to decode a
  renderer frame. Metadata: `%{reason: term(), format: atom()}`.
- `[:plushie, :bridge, :max_restarts_reached]` - renderer exceeded
  the maximum restart limit. Metadata: `%{reason: term(), max_restarts: integer()}`.

# `transport`

```elixir
@type transport() :: :spawn | :stdio | {:iostream, pid()}
```

# `bridge_for`

```elixir
@spec bridge_for(name :: atom()) :: atom()
```

Returns the registered name of the bridge for the given instance.

# `child_spec`

```elixir
@spec child_spec(keyword()) :: Supervisor.child_spec()
```

Child spec for embedding Plushie under an existing supervisor.

## Example

    children = [
      {Plushie, app: MyApp, name: :my_app_gui}
    ]

# `runtime_for`

```elixir
@spec runtime_for(name :: atom()) :: atom()
```

Returns the registered name of the runtime for the given instance.

# `start_link`

```elixir
@spec start_link(
  module(),
  keyword()
) :: Supervisor.on_start()
```

Starts a Plushie application under a supervisor linked to the calling process.

Returns `{:ok, pid}` on success.

# `stop`

```elixir
@spec stop(pid() | atom()) :: :ok
```

Stops a running Plushie supervisor.

Accepts a pid or the instance name passed as `:name` to `start_link/2`
(defaults to `Plushie`, matching the default registration).

---

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