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

The socket struct passed through all Mob.Screen and Mob.Component callbacks.

Holds two things:
- `assigns` — the public data map your `render/1` function reads via
  `assigns.foo`, or the `@foo` shorthand inside a `~MOB` template
  (the sigil rewrites `@foo` to `assigns.foo`, matching Phoenix HEEx)
- `__mob__` — internal Mob metadata (screen module, platform, view refs, nav stack)

You interact with a socket via `assign/2` and `assign/3`. Never mutate `__mob__`
directly — it is an internal contract.

# `platform`

```elixir
@type platform() :: :android | :ios
```

# `t`

```elixir
@type t() :: %Mob.Socket{
  __mob__: %{
    :screen =&gt; module() | nil,
    :platform =&gt; platform(),
    :root_view =&gt; term(),
    :view_tree =&gt; map(),
    :nav_stack =&gt; list(),
    :nav_action =&gt; term(),
    optional(:safe_area_confirmed) =&gt; boolean(),
    optional(:last_frame) =&gt; non_neg_integer(),
    optional(:list_renderers) =&gt; map(),
    optional(:async) =&gt; map()
  },
  assigns: map()
}
```

# `transition`

```elixir
@type transition() :: :push | :pop | :reset
```

# `assign`

```elixir
@spec assign(t(), keyword() | map()) :: t()
```

Assign multiple key/value pairs at once from a keyword list or map.

    socket = assign(socket, count: 0, name: "test")
    socket = assign(socket, %{count: 0})

# `assign`

```elixir
@spec assign(t(), atom(), term()) :: t()
```

Assign a single key/value pair into the socket's assigns.

    socket = assign(socket, :count, 0)

# `assign_new`

```elixir
@spec assign_new(t(), atom(), (-&gt; term())) :: t()
```

Assign `key` only if it is not already present, computing the value lazily.

    socket = assign_new(socket, :user, fn -> fetch_user(id) end)

`fun` runs only when `key` is absent, so it's the cheap way to set a default
or memoize a lookup across re-renders. Mirrors `Phoenix.LiveView.assign_new/3`.

# `cancel_async`

```elixir
@spec cancel_async(t(), term(), term()) :: t()
```

Stop the task `start_async/3` started under `name`. Mirrors
`Phoenix.LiveView.cancel_async/3`.

The screen's `handle_async/3` receives `{:exit, reason}`, even if the task had
already finished and its result was waiting to be delivered, unless a
`start_async/3` for the same name replaces it first. The task is stopped
whatever `reason` is; `reason` is only what `handle_async/3` is told. Does
nothing if no task is running under `name`.

# `new`

```elixir
@spec new(
  module(),
  keyword()
) :: t()
```

Create a new socket for the given screen module.

Options:
- `:platform` — `:android` (default) or `:ios`

# `pop_screen`

```elixir
@spec pop_screen(t()) :: t()
```

Pop the current screen, returning to the previous one.

No-op if already at the root of the stack.

# `pop_to`

```elixir
@spec pop_to(t(), atom() | module()) :: t()
```

Pop the stack until the screen registered under `dest` is at the top.

`dest` is a registered atom name or module. No-op if not found in history.

# `pop_to_root`

```elixir
@spec pop_to_root(t()) :: t()
```

Pop to the root of the current navigation stack.

# `push_screen`

```elixir
@spec push_screen(t(), atom() | module(), map()) :: t()
```

Push a new screen onto the navigation stack.

`dest` is either a registered atom name (e.g. `:counter`) or a screen module
(e.g. `MobDemo.CounterScreen`). `params` are passed to the new screen's
`mount/3` as the first argument.

The push is applied after the current callback returns — `do_render` in
`Mob.Screen` detects the nav_action and mounts the new module.

# `put_root_view`

```elixir
@spec put_root_view(t(), term()) :: t()
```

Store the root view ref returned by the renderer into `__mob__.root_view`.
Called internally after the initial render.

# `reset_to`

```elixir
@spec reset_to(t(), atom() | module(), map(),
  transition: transition(),
  scope: :stack | :all
) :: t()
```

Replace the current navigation stack with a single new screen.

Used for auth transitions (post-login → home with no back button to login).
Pass `transition: :push` or `transition: :pop` when the reset still represents
directional movement, such as switching between custom tabs. The default,
`:reset`, cross-fades. Pass `scope: :all` for an auth boundary that must also
discard every parked tab stack. The default `scope: :stack` preserves the
established current-stack-only behavior.

Raises `ArgumentError` on any other transition — including `:none`, which
would replace the stack while telling the platform no navigation happened,
leaving the incoming screen wearing the outgoing one's view identities.

# `start_async`

```elixir
@spec start_async(t(), term(), (-&gt; term())) :: t()
```

Run `fun` in a separate process and deliver its result to the screen's
`c:Mob.Screen.handle_async/3`. Mirrors `Phoenix.LiveView.start_async/3`.

The way to load data without holding up the screen: `mount/3` runs before
the screen's first frame, so slow work there delays the screen itself. Assign
a placeholder, start the work, and render the placeholder (a spinner, or
skeleton boxes) until the result arrives:

    def mount(%{id: id}, _session, socket) do
      {:ok,
       socket
       |> Mob.Socket.assign(:profile, :loading)
       |> Mob.Socket.start_async(:profile, fn -> MyApp.Api.fetch_profile(id) end)}
    end

    def handle_async(:profile, {:ok, profile}, socket),
      do: {:noreply, Mob.Socket.assign(socket, :profile, profile)}

    def handle_async(:profile, {:exit, reason}, socket),
      do: {:noreply, Mob.Socket.assign(socket, :profile, {:failed, reason})}

`handle_async/3` receives `{:ok, result}` with whatever `fun` returned, or
`{:exit, reason}` if `fun` raised, threw or exited, or the task was cancelled
with `cancel_async/3`. A crashing task does not crash the screen. Neither
outcome reaches `handle_info/2`.

`name` is any term. Starting a name whose task is still running replaces it:
the older task is killed and only the newest reports, so a search that starts
a lookup on every keystroke never renders a stale result.

The task is linked to the screen, so it stops when the screen does, whether
the screen crashes or is popped off the navigation stack. Raises
`ArgumentError` if the screen does not define `handle_async/3`.

# `switch_tab`

```elixir
@spec switch_tab(t(), atom()) :: t()
```

Switch to the named tab in a tab_bar or drawer layout.

By default, switching tabs has no animation. Pass `transition: :push`,
`transition: :pop`, or `transition: :reset` when the tab order implies
directional movement or a cross-fade. `mount_params: %{...}` supplies the
params for the target root's first mount. A previously mounted stack ignores
later mount params and restores its existing screen state.

# `switch_tab`

```elixir
@spec switch_tab(t(), atom(), transition: transition(), mount_params: map()) :: t()
```

# `update`

```elixir
@spec update(t(), atom(), (term() -&gt; term())) :: t()
```

Update an existing assign by applying `fun` to its current value.

    socket = update(socket, :count, fn count -> count + 1 end)

Raises `KeyError` if `key` is not already assigned. Mirrors
`Phoenix.LiveView.update/3`.

---

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