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.
Summary
Functions
Returns a specification to start this module under a supervisor.
Dispatch a UI event to the screen process. Returns :ok synchronously once
the event has been processed and the state updated.
Return the module of the currently active screen in the navigation stack. Intended for testing and debugging.
Return the navigation history (list of {module, socket} pairs, head = most recent).
Intended for testing and debugging.
Return the pid of the process owning the currently active screen.
Return the current socket state of a running screen, or nil while that
screen is being restarted.
Start a screen process linked to the calling process.
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.
Functions
Returns a specification to start this module under a supervisor.
See Supervisor.
Dispatch a UI event to the screen process. Returns :ok synchronously once
the event has been processed and the state updated.
Return the module of the currently active screen in the navigation stack. Intended for testing and debugging.
@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.
@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.
@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.
@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).