# `Mob.Screen`
[🔗](https://github.com/genericjam/mob/blob/master/lib/mob/screen.ex#L1)

Behaviour and GenServer wrapper for a Mob screen.

Each screen runs as a supervised GenServer whose state is a `Mob.Socket`.
Putting one process per screen — instead of one big process for the whole
app — gives you isolation: a buggy `handle_event` crashes its own screen
and the supervisor restarts it without taking down navigation, audio,
background services, or the BEAM itself. Lifecycle callbacks (`mount`,
`render`, `handle_event`, `handle_info`, `terminate`) map directly to the
GenServer lifecycle, so the BEAM's existing concurrency tools (selective
receive, monitors, hot code push) work on screens without any Mob-specific
scaffolding.

## Usage

    defmodule MyApp.CounterScreen do
      use Mob.Screen

      def mount(_params, _session, socket) do
        {:ok, Mob.Socket.assign(socket, :count, 0)}
      end

      def render(assigns) do
        %{
          type: :column,
          props: %{},
          children: [
            %{type: :text, props: %{text: "Count: #{assigns.count}"}, children: []}
          ]
        }
      end

      def handle_event("increment", _params, socket) do
        {:noreply, Mob.Socket.assign(socket, :count, socket.assigns.count + 1)}
      end
    end

## Starting a screen

    {:ok, pid} = Mob.Screen.start_link(MyApp.CounterScreen, %{})

## Dispatching events

    :ok = Mob.Screen.dispatch(pid, "increment", %{})

# `socket`

```elixir
@type socket() :: Mob.Socket.t()
```

# `dump_state`

```elixir
@callback dump_state(assigns :: map()) :: map()
```

Serialise assigns for persistence. Return a plain map of the keys you want
restored on next launch. Defaults to the full assigns map minus any
non-serialisable values (PIDs, references, ports, functions).

Only called when `use Mob.Screen, vsn: N` (N > 0) or `persist: true`.

# `handle_event`
*optional* 

```elixir
@callback handle_event(event :: String.t(), params :: map(), socket :: socket()) ::
  {:noreply, socket()} | {:reply, map(), socket()}
```

# `handle_info`
*optional* 

```elixir
@callback handle_info(message :: term(), socket :: socket()) :: {:noreply, socket()}
```

# `load_state`

```elixir
@callback load_state(stored_vsn :: non_neg_integer(), stored :: map()) :: map()
```

Reconstruct assigns from a previously persisted map.

`stored_vsn` is the version that was current when the data was saved.
Match on it to migrate old shapes:

    def load_state(1, stored), do: stored
    def load_state(0, stored), do: Map.put(stored, :new_field, :default)

The returned map is merged into the socket's assigns after `mount/3` runs.
Only called when stored data exists.

# `mount`

```elixir
@callback mount(params :: map(), session :: map(), socket :: socket()) ::
  {:ok, socket()} | {:error, term()}
```

# `render`

```elixir
@callback render(assigns :: map()) :: map()
```

# `screen_key`
*optional* 

```elixir
@callback screen_key(assigns :: map()) :: String.t()
```

Return a stable string key for storing this screen's state.

Implement when the same screen module holds per-user or parameterised state:

    def screen_key(assigns), do: "#{__MODULE__}:#{assigns.user_id}"

Defaults to the module name string.

# `terminate`
*optional* 

```elixir
@callback terminate(reason :: term(), socket :: socket()) :: term()
```

# `child_spec`

Returns a specification to start this module under a supervisor.

See `Supervisor`.

# `dispatch`

```elixir
@spec dispatch(pid(), String.t(), map()) :: :ok
```

Dispatch a UI event to the screen process. Returns `:ok` synchronously once
the event has been processed and the state updated.

# `get_current_module`

```elixir
@spec get_current_module(pid()) :: module()
```

Return the module of the currently active screen in the navigation stack.
Intended for testing and debugging.

# `get_nav_history`

```elixir
@spec get_nav_history(pid()) :: [{module(), Mob.Socket.t()}]
```

Return the navigation history (list of `{module, socket}` pairs, head = most recent).
Intended for testing and debugging.

# `get_socket`

```elixir
@spec get_socket(pid()) :: socket()
```

Return the current socket state of a running screen.
Intended for testing and debugging — not for production app logic.

# `handle_call`

Apply a navigation action directly. Used by `Mob.Test` to drive navigation
programmatically without needing a UI event. Synchronous — the caller blocks
until the navigation (and re-render, in production mode) completes.

Valid actions mirror the `Mob.Socket` navigation functions:
- `{:push, dest, params}` — push a new screen
- `{:pop}` — pop to the previous screen
- `{:pop_to, dest}` — pop to a specific screen in history
- `{:pop_to_root}` — pop to the root of the current stack
- `{:reset, dest, params}` — replace the entire nav stack

# `start_link`

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

Start a screen process linked to the calling process.

`params` is passed as the first argument to `mount/3`.

# `start_root`

```elixir
@spec start_root(module(), map(), keyword()) :: GenServer.on_start()
```

Start a screen as the root UI screen. Calls mount, renders the component tree
via `Mob.Renderer`, and calls `set_root` on the resulting view.

This is the main entry point for production use. `start_link/2` is for tests
(no NIF calls).

---

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