# `Kino.ExRatatui`
[🔗](https://github.com/mcass19/kino_ex_ratatui/blob/v0.2.1/lib/kino/ex_ratatui.ex#L1)

Run an `ExRatatui.App` inside a Livebook notebook via xterm.js.

`Kino.ExRatatui` is a byte-stream transport that pipes the runtime
server's rendered ANSI through an xterm.js iframe and forwards
keypresses + resize events back.

## Example

    defmodule Counter do
      use ExRatatui.App
      alias ExRatatui.Event.Key
      alias ExRatatui.Layout.Rect
      alias ExRatatui.Widgets.Paragraph

      def mount(_), do: {:ok, %{n: 0}}

      def render(state, frame) do
        [{%Paragraph{text: "Count: #{state.n}"},
          %Rect{x: 0, y: 0, width: frame.width, height: frame.height}}]
      end

      def handle_event(%Key{code: "+"}, s), do: {:noreply, %{s | n: s.n + 1}}
      def handle_event(%Key{code: "q"}, s), do: {:stop, s}
      def handle_event(_, s),                do: {:noreply, s}
    end

    Kino.ExRatatui.new(Counter)

## Mount options

The second argument to `new/2` is a keyword list. Any key not listed
under [Display options](#module-display-options) below is forwarded
verbatim to `c:ExRatatui.App.mount/1`. Use it for per-instance
configuration your App reads from its mount opts.

The keys `:mod`, `:name`, and `:transport` are reserved by the
runtime and silently overwritten.

## Display options

Reserved opts that configure the xterm.js iframe rather than the App.
All are optional; defaults preserve the current widget look. Unknown
values raise `ArgumentError` at the call site.

| Option | Type | Default | Notes |
| ------ | ---- | ------- | ----- |
| `:theme` | `map()` or `:dark` / `:light` / `:livebook` | catppuccin-style dark theme | A map is forwarded to xterm.js's `Terminal({theme: ...})` verbatim — accepts the full xterm.js [`ITheme`](https://xtermjs.org/docs/api/terminal/interfaces/itheme/) object (`:background`, `:foreground`, `:cursor`, `:cursorAccent`, `:selectionBackground`, the 16-color ANSI palette, …). Atom keys are JSON-encoded as strings; use the camelCase xterm.js expects (`cursorAccent`, not `cursor_accent`). The atom shorthands resolve in the JS hook: `:dark` and `:light` pick a bundled palette, `:livebook` follows the user's `prefers-color-scheme` and live-switches when it changes. |
| `:font_family` | `String.t()` | `"ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace"` | CSS `font-family` value. Anything `xterm.js` can render. |
| `:font_size` | `pos_integer()` | `13` | Cell font size in px. |
| `:height` | `String.t()` | `"400px"` | CSS height applied to the xterm container. Accepts any valid CSS length (`"600px"`, `"60vh"`, …). |
| `:cursor_blink` | `boolean()` | `true` | xterm.js cursor blink. |
| `:scrollback` | `non_neg_integer()` | `1000` | xterm.js scrollback line limit. |
| `:stopped_message` | `String.t()` | `"App stopped — re-evaluate the cell to start a new run."` | Shown in the iframe after the runtime exits (`{:stop, _}` or `mount/1` failure). |

Example:

    Kino.ExRatatui.new(Counter,
      # mount opts → App.mount/1
      start: 10,
      # display opts → xterm.js
      theme: %{background: "#0d1117", foreground: "#c9d1d9"},
      font_size: 14,
      height: "600px"
    )

## Global configuration

Set defaults that apply to every `new/2` and `frame/2` call in the
current Livebook (or your application's runtime) with `configure/1`:

    # In a setup cell, or your application's start/2:
    Kino.ExRatatui.configure(
      theme: :livebook,
      font_family: "JetBrains Mono, ui-monospace, monospace",
      font_size: 14
    )

Merge order is **per-instance opts > `configure/1` > module
defaults**, key by key. Setting `theme:` globally and then passing
`theme: %{background: "#000"}` per-instance overrides the theme
alone — the global `font_size` still wins for that call. See
`configure/1` for the full list and the
[Configuration guide](configuration.md) for recipes.

## Lifecycle

Each `new/2` call spawns a fresh `Kino.JS.Live` server. The runtime
server and `ExRatatui.Session` are created lazily on the first
`"resize"` event from the iframe so we always start at the correct
cell dimensions reported by xterm.js's FitAddon. Re-evaluating the
cell (or closing the notebook) tears the runtime server down and
starts a new one — no state is preserved across re-evals.

## How it plugs into ExRatatui

Implements `ExRatatui.Transport` as a byte-stream transport, using
`ExRatatui.Transport.start_server/1` to boot the runtime and
`ExRatatui.Transport.ByteStream` to pump input + resize events. See
the [Custom Transports
guide](https://hexdocs.pm/ex_ratatui/custom_transports.html) for the
reference shape.

## Inline images

The bundled JS hook loads `@xterm/addon-image`, so
`ExRatatui.Widgets.Image` renders end-to-end in Livebook over Sixel
and iTerm2 inline-image protocols. Build images with
`ExRatatui.Image.new/2` and place them in the widget tree like any
other widget — no kino-side configuration is needed. The protocol is
picked explicitly at construction time; pass `:sixel` or `:iterm2`
(xterm.js does not implement the Kitty graphics protocol). See the
[Images guide](https://hexdocs.pm/ex_ratatui/images.html) for the
full API.

# `configure`

```elixir
@spec configure(keyword()) :: :ok
```

Sets global defaults applied to every subsequent `new/2` and
`frame/2` call in the current runtime.

Accepts the same keys as the [Display options](#module-display-options)
on `new/2`. Each value is validated immediately (the same way it
would be on `new/2`) — bad shapes raise `ArgumentError`.

Per-instance opts on `new/2` / `frame/2` still win key-by-key.
Calling `configure/1` again merges into the existing config rather
than replacing it, so you can split related settings across cells.

Returns `:ok`.

## Examples

    # Reactively follow Livebook's light/dark mode and bump the font.
    Kino.ExRatatui.configure(theme: :livebook, font_size: 14)

    # Per-instance overrides still apply. This call uses the configured
    # font_size: 14 but a custom theme.
    Kino.ExRatatui.new(Counter, theme: %{background: "#000"})

Stored under the `:kino_ex_ratatui` Application environment, so the
same value is also reachable via `Application.get_all_env(:kino_ex_ratatui)`
if you're orchestrating from a release `config/runtime.exs` or any
other `Config`-driven setup.

# `frame`

```elixir
@spec frame(
  [{ExRatatui.widget(), ExRatatui.Layout.Rect.t()}],
  keyword()
) :: Kino.JS.t()
```

Renders a one-shot static frame of widgets and returns a
non-interactive `Kino.JS` widget that paints it once.

Useful for documentation, screenshots in notebooks, or
`Kino.Layout.grid([frame_a, frame_b, frame_c])` side-by-side
comparisons. There is no event loop, no resize handling, and no
runtime server — just an `ExRatatui.Session` rendered once and
written to xterm.js.

## Options

  * `:cols` — terminal width in cells. Defaults to `80`.
  * `:rows` — terminal height in cells. Defaults to `24`.
  * `:theme` — see [Display options](#module-display-options). Same
    defaults as `new/2`.
  * `:font_family` — same.
  * `:font_size` — same.

Live-only display opts (`:height`, `:cursor_blink`, `:scrollback`,
`:stopped_message`) are not accepted here and will raise
`ArgumentError` — they have nothing to apply against in a static
one-shot frame.

## Examples

    iex> alias ExRatatui.Layout.Rect
    iex> alias ExRatatui.Widgets.{Block, Paragraph}
    iex> kino = Kino.ExRatatui.frame(
    ...>   [
    ...>     {%Paragraph{
    ...>        text: "Hello from a static frame!",
    ...>        block: %Block{title: "demo"}
    ...>      },
    ...>      %Rect{x: 0, y: 0, width: 40, height: 5}}
    ...>   ],
    ...>   cols: 40,
    ...>   rows: 5
    ...> )
    iex> kino.module
    Kino.ExRatatui

# `new`

```elixir
@spec new(
  module(),
  keyword()
) :: Kino.JS.Live.t()
```

Builds a new live kino that hosts `mod` (an `ExRatatui.App`).

`opts` is split into [Display options](#module-display-options)
consumed by the widget itself and [Mount options](#module-mount-options)
forwarded verbatim to `c:ExRatatui.App.mount/1`. Reserved display
keys never reach the App.

## Examples

    # Plain
    Kino.ExRatatui.new(Counter)

    # Mount opt forwarded to mount/1
    Kino.ExRatatui.new(Counter, start: 10)

    # Display opt + mount opt
    Kino.ExRatatui.new(Counter,
      start: 10,
      theme: %{background: "#0d1117", foreground: "#c9d1d9"},
      font_size: 14
    )

---

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