# `PhoenixExRatatui.LiveComponent`
[🔗](https://github.com/mcass19/phoenix_ex_ratatui/blob/v0.2.0/lib/phoenix_ex_ratatui/live_component.ex#L1)

Macro that turns the calling module into a `Phoenix.LiveComponent`
hosting an embedded TUI — the same module is both the component and
the `ExRatatui.App` it drives.

Where `PhoenixExRatatui.LiveView` makes the page itself a TUI, this
macro drops a TUI inside an existing LiveView alongside whatever else
that LV is rendering — admin dashboards, dev consoles, half-page
overlays.

## Quick start

    defmodule MyAppWeb.AdminCounterPanel do
      use PhoenixExRatatui.LiveComponent

      def tui_mount(_opts), do: {:ok, %{count: 0}}
      def tui_render(state, frame), do: [...]
      def tui_handle_event(_event, state), do: {:noreply, state}
    end

    # In a parent LiveView:
    defmodule MyAppWeb.AdminLive do
      use Phoenix.LiveView

      def render(assigns) do
        ~H"""
        <h1>Admin Dashboard</h1>
        <.live_component module={MyAppWeb.AdminCounterPanel} id="admin-counter" />
        <p>Other admin content</p>
        """
      end
    end

## How it works

Same trick as `PhoenixExRatatui.LiveView`: the macro injects
`Phoenix.LiveComponent` callbacks AND, via `@after_compile`,
generates a sibling `Module.Runtime` proxy that conforms to
`ExRatatui.App` by delegating to the `tui_*` callbacks. The
`handle_info/2` arity collision between LV and ExRatatui.App is
the same in LiveComponents (well, LCs don't have handle_info, but
the abstraction stays consistent).

## TUI callbacks

The App-level `tui_*` callbacks are the same as
`PhoenixExRatatui.LiveView`:

  * `tui_mount/1`, `tui_render/2`, `tui_handle_event/2`,
    `tui_handle_info/2`, `tui_terminate/2`, `tui_mount_opts/1`

See `PhoenixExRatatui.LiveView`'s moduledoc for full callback
semantics.

Two extra hooks are LiveComponent-specific, for your own *component*
logic (named distinctly from the App-level callbacks above so they never
collide — e.g. with the reducer runtime's `tui_update/2`):

  * `tui_component_update/2` — `(assigns, socket)`, the parent's assigns
    for this component. Default `{:ok, assign(socket, assigns)}`. The
    library owns `update/2` to keep the render path intact, so override
    this instead of `update/2`.
  * `tui_component_event/3` — `(event, params, socket)`, for the
    component's own `phx-click`s and similar. Default `{:noreply,
    socket}`. The library owns `handle_event/3` for the TUI's input, so
    override this instead of `handle_event/3`.

## Threading parent assigns into the App

The default `tui_component_update/2` already assigns the parent's assigns
to the socket, so `tui_mount_opts/1` (which gets the component socket)
can read them and thread them into `tui_mount/1`:

    def tui_mount_opts(socket), do: [user: socket.assigns.user]

    def tui_mount(opts), do: {:ok, %{user: opts[:user]}}

Override `tui_component_update/2` only when you need to transform or
derive from those assigns before they're stored.

## Wire model

LiveComponents share the parent LV's process — they don't have
their own mailbox or `handle_info/2`. We hand the runtime Server a
writer that calls `Phoenix.LiveView.send_update/3` instead of
`send/2`. Each rendered diff arrives in `update(%{tui_diff: diff},
socket)` and flows out to the client via `push_event/3`.

Mount-failure handling matches the LiveView macro: if `tui_mount/1`
returns `{:error, _}`, the runtime server's init returns
`{:stop, reason}`, the linked parent LV gets the EXIT, and we set
`:tui_error` so the next render shows a fallback. **The parent LV
must trap exits** for this to work — otherwise the EXIT signal
kills the whole LV. We set `Process.flag(:trap_exit, true)` lazily
on the first resize event before calling `Transport.start_link/1`.
This sets it on the parent LV process; if that LV traps exits for
other reasons, the flag is shared.

## Intents and the parent LV

When the embedded TUI emits intents (e.g. returning `{:noreply, state,
intents: [{:navigate, "/login"}]}` from `tui_handle_event/2`), the
intent must dispatch from the **parent LV's process**, not the
LiveComponent — Phoenix LV forbids redirects from inside
`LiveComponent.update/2`.

This module's intent_writer therefore sends a regular message to
the parent LV: `send(parent_pid, {:phoenix_ex_ratatui, :intent,
intent})`. The parent LV must have a `handle_info/2` clause that
picks it up:

    def handle_info({:phoenix_ex_ratatui, :intent, intent}, socket) do
      {:noreply, PhoenixExRatatui.LiveView.dispatch_intent(socket, intent)}
    end

`PhoenixExRatatui.LiveView.dispatch_intent/2` handles the standard
intent shapes (`{:navigate, _}`, `{:patch, _}`, `{:redirect, _}`).

When the LC is embedded inside another `PhoenixExRatatui.LiveView`
(rare but supported), this clause is generated automatically. For the
common case — embedding inside a plain `Phoenix.LiveView` — copy the
snippet above.

## Telemetry

Same events as `PhoenixExRatatui.LiveView` — see
`PhoenixExRatatui.Telemetry`.

# `__using__`
*macro* 

Generates the unified LiveComponent + App module. See the moduledoc.

---

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