Search documentation of Plushie

# Plushie.Bridge (Plushie v0.7.2)

Copy Markdown View Source

Bridge to the plushie renderer process.

Manages the connection to the renderer, buffers partial JSONL lines (JSON mode) or receives length-prefixed frames (MessagePack mode), and forwards decoded events to the runtime process.

## Transport modes

Transport I/O is delegated to modules implementing `Plushie.Transport`. Controlled by the `:transport` option:

- `:spawn` (default) - spawns the renderer binary as a child process using an Erlang Port (`Plushie.Transport.Port`).

- `:stdio` - reads/writes the BEAM's own stdin/stdout. Used when the renderer spawns the Elixir process (`Plushie.Transport.Port`).

- `{:iostream, pid}` - sends and receives protocol messages via an external process (`Plushie.Transport.IOStream`). See that module for the adapter protocol.

## Wire formats

Controlled by the `:format` option:

- `:json` - JSONL over stdio. Opt-in for debugging and observability.
- `:msgpack` (default) - MessagePack with 4-byte length-prefixed framing.

On unexpected exit the bridge applies exponential back-off and attempts to restart the renderer up to `max_restarts` times. If the limit is exhausted the GenServer stops with `{:max_restarts_reached, reason}`.

During restart the runtime rebuilds renderer-owned state by re-sending settings, a full snapshot, subscriptions, and windows. Transient commands that cannot be rebuilt from that state are held until the runtime finishes resync, then sent in order.

## Summary

### Functions

child_spec(init_arg)

Returns a specification to start this module under a supervisor.

restart_renderer(bridge)

Restarts the renderer process intentionally (e.g. after a Rust rebuild).

screenshot(bridge, name, opts \\ [], timeout \\ 30000)

Captures a renderer screenshot and returns the raw response map.

send_advance_frame(bridge, timestamp)

Sends an advance_frame message to the renderer (headless/test mode).

send_command(bridge, id, family, value)

Sends a widget-targeted command to the renderer.

send_commands(bridge, commands)

Sends a batch of widget-targeted commands to the renderer.

send_effect(bridge, id, kind, payload)

Sends an effect request to the renderer.

send_image_op(bridge, op, payload)

Sends an image operation (create/update/delete) to the renderer.

send_interact(bridge, id, action, selector, payload \\ %{})

Sends an interact request to the renderer.

send_load_font(bridge, family, data)

Sends a font-load message to the renderer.

send_patch(bridge, ops)

Sends a patch (list of diff ops) to the renderer.

send_register_effect_stub(bridge, kind, response)

Registers an effect stub with the renderer.

send_settings(bridge, settings)

Sends application-level settings to the renderer.

send_snapshot(bridge, tree)

Sends an encoded snapshot of `tree` to the renderer.

send_subscribe(bridge, kind, tag, max_rate \\ nil, window_id \\ nil)

Subscribes to a renderer-side event source.

send_system_op(bridge, op, settings \\ %{})

Sends a system-wide operation to the renderer.

send_system_query(bridge, op, settings \\ %{})

Sends a system-wide query to the renderer.

send_unregister_effect_stub(bridge, kind)

Removes a previously registered effect stub.

send_unsubscribe(bridge, kind, tag \\ nil)

Unsubscribes from a renderer-side event source.

send_widget_op(bridge, op, payload)

Sends a widget operation to the renderer.

send_window_op(bridge, op, window_id, settings \\ %{})

Sends a window lifecycle operation to the renderer.

start_link(opts)

Starts the bridge linked to the calling process.

stop(bridge)

Stops the bridge GenServer.

## Functions

# child_spec(init_arg)

Returns a specification to start this module under a supervisor.

See `Supervisor`.

# restart_renderer(bridge)

    @spec restart_renderer(bridge :: GenServer.server()) :: :ok

Restarts the renderer process intentionally (e.g. after a Rust rebuild).

Unlike crash recovery, this does not count against the restart limit and does not use exponential backoff. The existing renderer is closed cleanly before opening the new one. The runtime receives `:renderer_restarted` and re-syncs as usual.

# screenshot(bridge, name, opts \\ [], timeout \\ 30000)

    @spec screenshot(
      bridge :: GenServer.server(),
      name :: String.t(),
      opts :: keyword(),
      timeout :: timeout()
    ) :: map()

Captures a renderer screenshot and returns the raw response map.

Width and height are optional positive integers. The call blocks until the renderer replies with `screenshot_response`.

# send_advance_frame(bridge, timestamp)

    @spec send_advance_frame(bridge :: GenServer.server(), timestamp :: non_neg_integer()) ::
      :ok

Sends an advance_frame message to the renderer (headless/test mode).

# send_command(bridge, id, family, value)

    @spec send_command(
      bridge :: GenServer.server(),
      id :: String.t(),
      family :: String.t(),
      value :: term()
    ) :: :ok

Sends a widget-targeted command to the renderer.

# send_commands(bridge, commands)

    @spec send_commands(
      bridge :: GenServer.server(),
      commands :: [{String.t(), String.t(), term()}]
    ) :: :ok

Sends a batch of widget-targeted commands to the renderer.

# send_effect(bridge, id, kind, payload)

    @spec send_effect(
      bridge :: GenServer.server(),
      id :: String.t(),
      kind :: String.t(),
      payload :: map()
    ) :: :ok

Sends an effect request to the renderer.

# send_image_op(bridge, op, payload)

    @spec send_image_op(bridge :: GenServer.server(), op :: String.t(), payload :: map()) ::
      :ok

Sends an image operation (create/update/delete) to the renderer.

# send_interact(bridge, id, action, selector, payload \\ %{})

    @spec send_interact(
      bridge :: GenServer.server(),
      id :: String.t(),
      action :: String.t(),
      selector :: map(),
      payload :: map()
    ) :: :ok

Sends an interact request to the renderer.

The renderer will process the interaction against its widget tree and respond with `interact_step` / `interact_response` messages. These are forwarded to the runtime as `{:interact_step, id, events}` and `{:interact_response, id, events}`.

## Parameters

- `id` - unique request identifier, used to correlate responses.
- `action` - the interaction verb. One of: `"click"`, `"toggle"`, `"select"`, `"type_text"`, `"submit"`, `"press"`, `"release"`, `"type_key"`, `"slide"`, `"paste"`, `"scroll"`, `"move_to"`, `"sort"`, `"canvas_press"`, `"canvas_release"`, `"canvas_move"`, `"pane_focus_cycle"`.
- `selector` - a map identifying the target widget. Keys are optional and include `"by"` (e.g. `"id"`, `"text"`, `"role"`, `"label"`, `"focused"`) and `"value"` (the lookup value). An empty map targets the focused widget or the window root.
- `payload` - action-specific data. Examples:
  - `%{text: "hello"}` for `"type_text"` / `"paste"`
  - `%{value: "option"}` for `"select"`
  - `%{value: 0.5}` for `"slide"`
  - `%{key: "Enter", modifiers: %{}}` for `"press"` / `"release"` / `"type_key"`
  - `%{x: 10, y: 20, button: "left"}` for `"canvas_press"` / `"canvas_release"`
  - `%{x: 10, y: 20}` for `"canvas_move"` / `"move_to"`
  - `%{delta_x: 0, delta_y: -3}` for `"scroll"`
  - `%{column: "name", direction: "asc"}` for `"sort"`
  - `%{}` for `"click"`, `"toggle"`, `"submit"`, `"pane_focus_cycle"`

# send_load_font(bridge, family, data)

    @spec send_load_font(
      bridge :: GenServer.server(),
      family :: String.t(),
      data :: binary()
    ) :: :ok

Sends a font-load message to the renderer.

# send_patch(bridge, ops)

    @spec send_patch(bridge :: GenServer.server(), ops :: [map()]) :: :ok

Sends a patch (list of diff ops) to the renderer.

# send_register_effect_stub(bridge, kind, response)

    @spec send_register_effect_stub(
      bridge :: GenServer.server(),
      kind :: String.t(),
      response :: term()
    ) :: :ok

Registers an effect stub with the renderer.

# send_settings(bridge, settings)

    @spec send_settings(bridge :: GenServer.server(), settings :: map()) :: :ok

Sends application-level settings to the renderer.

# send_snapshot(bridge, tree)

    @spec send_snapshot(bridge :: GenServer.server(), tree :: map()) :: :ok

Sends an encoded snapshot of `tree` to the renderer.

# send_subscribe(bridge, kind, tag, max_rate \\ nil, window_id \\ nil)

    @spec send_subscribe(
      bridge :: GenServer.server(),
      kind :: String.t(),
      tag :: String.t(),
      max_rate :: non_neg_integer() | nil,
      window_id :: String.t() | nil
    ) :: :ok

Subscribes to a renderer-side event source.

# send_system_op(bridge, op, settings \\ %{})

    @spec send_system_op(
      bridge :: GenServer.server(),
      op :: String.t(),
      settings :: map()
    ) :: :ok

Sends a system-wide operation to the renderer.

# send_system_query(bridge, op, settings \\ %{})

    @spec send_system_query(
      bridge :: GenServer.server(),
      op :: String.t(),
      settings :: map()
    ) :: :ok

Sends a system-wide query to the renderer.

# send_unregister_effect_stub(bridge, kind)

    @spec send_unregister_effect_stub(bridge :: GenServer.server(), kind :: String.t()) ::
      :ok

Removes a previously registered effect stub.

# send_unsubscribe(bridge, kind, tag \\ nil)

    @spec send_unsubscribe(
      bridge :: GenServer.server(),
      kind :: String.t(),
      tag :: String.t() | nil
    ) :: :ok

Unsubscribes from a renderer-side event source.

# send_widget_op(bridge, op, payload)

    @spec send_widget_op(bridge :: GenServer.server(), op :: String.t(), payload :: map()) ::
      :ok

Sends a widget operation to the renderer.

# send_window_op(bridge, op, window_id, settings \\ %{})

    @spec send_window_op(
      bridge :: GenServer.server(),
      op :: String.t(),
      window_id :: String.t(),
      settings :: map()
    ) :: :ok

Sends a window lifecycle operation to the renderer.

# start_link(opts)

    @spec start_link(opts :: keyword()) :: GenServer.on_start()

Starts the bridge linked to the calling process.

Required opts:

- `:runtime` - pid to receive `{:renderer_event, event}` messages

Required for `:spawn` transport (default):

- `:renderer_path` - filesystem path to the plushie binary

Optional opts:

- `:name` - registration name passed to `GenServer.start_link/3`
- `:transport` - `:spawn` (default, spawns renderer as child process),

                     `:stdio` (reads/writes the BEAM's own stdin/stdout),
                     or `{:iostream, pid}` (custom transport via iostream adapter)

- `:format` - wire format, `:msgpack` (default) or `:json`
- `:log_level` - renderer log level (`:off`, `:error`, `:warning`, `:info`, `:debug`).

                     Default: `:error`. Ignored when `RUST_LOG` is set in the environment.

- `:renderer_args` - extra CLI args prepended to the renderer command (e.g. `["--headless"]`)
- `:max_restarts` - max restart attempts before giving up (default: 5)
- `:restart_delay` - base delay in ms for exponential back-off (default: 100)
- `:heartbeat_interval` - maximum time (ms) between renderer messages before

                     the bridge considers the renderer unresponsive and triggers
                     a restart. `nil` disables the watchdog. Default: `30_000`.

# stop(bridge)

    @spec stop(bridge :: GenServer.server()) :: :ok

Stops the bridge GenServer.