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

Owns navigation, and the one process per live screen that serves it.

Which stacks exist, which is active, and one `Mob.Screen.Server` per live
screen — this process starts them, stops them, and restarts one that crashes.
It keeps the `:mob_screen` registered name, so the native layer's
`enif_whereis_pid` lookups (back gesture, alert actions, launch
notifications) are unaffected.

## Not in the per-message path

A screen handling an ordinary message never touches this process. Native
events reach a screen directly: `Mob.Listener` unwraps the envelope and sends
to the screen's own pid, the screen renders, and `Mob.Sender` commits. The
router hears only about navigation.

That is the property MOB-113 exists to guarantee, and it is what makes one
process per screen affordable. An earlier costing of this design assumed a
router in the loop and concluded per-screen processes could not escape a hop
per message; splitting the router from the sender is what dissolved that.

`Mob.Screen` delegates its public API here, so callers keep using
`Mob.Screen.dispatch/3` and friends.

## A navigation entry

`%{module:, pid:, params:, ref:}`.

`params` is carried because a restart has to reproduce the screen exactly —
one that mounts on `%{id: id}` cannot come back from `%{}`.

`ref` identifies the screen to `Mob.Sender`, and is unique **per screen**,
not per stack. Every screen is a live process that repaints on any message it
receives, including the ones below the top of a stack; keyed by stack, a timer
tick in a screen the user cannot see would commit its tree — tap table
included — over the screen they can. The sender only commits the tree whose
ref is active. The ref survives a restart, because the replacement is the same
logical screen.

See `decisions/2026-08-28-screen-processes-and-supervision.md`.

# `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() | nil}]
```

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

# `get_screen_pid`

```elixir
@spec get_screen_pid(GenServer.server()) :: pid()
```

Return the pid of the process owning the currently active screen.

Each live screen is its own process since MOB-112; this is how tooling
reaches the one that is on screen.

# `get_socket`

```elixir
@spec get_socket(pid()) :: Mob.Socket.t() | nil
```

Return the current socket state of a running screen, or `nil` while that
screen is being restarted.

Intended for testing and debugging — not for production app logic.

# `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*
