Which window scenes exist, and which Mob.Router shows each (MOB-245).
iPadOS can show several windows of one app, each a UIWindowScene. All of
them run in the one BEAM; each gets its own router, navigation and screen
processes. This process keeps the map and owns the lifecycle:
- The primary router is the one the app starts with
Mob.Screen.start_root/2. It registers here (register_primary/4), handing over its root module and params, the template every further window starts from. It holds:mob_screenand is never bound to a scene id: it renders to native's default scene, the way a single-window app always has. - Native reports scenes as
{:mob_scene, :connected, id, default?}and{:mob_scene, :disconnected, id}. Native can't message the BEAM before it is up, so this process also asksmob_nif:scenes/0when it starts. - A connecting scene that a router already shows is told its window is back. Native's default scene goes to the unbound primary. Otherwise a new router is started for it, bound to its id, from the primary's root module and params.
- A disconnecting scene has its router stopped, unless it was the last
attached scene: that router is kept, so the window comes back as it
was. When the primary's window goes and others remain,
:mob_screenmoves to the oldest remaining router before the primary stops. - A new session replacing the kept one (iPadOS made a new session
after the user discarded the last window's) arrives as
{:mob_scene, :replaced, old_id, new_id, default?}: native hands the new scene the old one's tap set and model, and the old id's router moves to the new id. - A kept scene native discards (its session is gone, e.g. removed in
the app switcher) arrives as
{:mob_scene, :discarded, id}: its router stops, unless it is the app's only one. - Window events native can attribute to a scene — the back gesture,
size class changes, alert results, WebView messages — arrive as
{:mob_scene_event, id, message}and are forwarded to that scene's router. One no router shows (a window over native's limit, or one whose router failed to start) is dropped: no other window's router may act on it. Only before the primary has registered does it go to:mob_screen, the one router there can be.
Scene ids are UISceneSession.persistentIdentifier strings, nil on
platforms without scenes. Mob.Test.screens/1 reads screens/0. See
decisions/2026-10-02-one-router-per-window-scene.md.
Summary
Types
A window scene id (UISceneSession.persistentIdentifier), nil where there is none.
Functions
Returns a specification to start this module under a supervisor.
Every live router as {scene_id, router_pid}: the primary first, then one
per further window scene in the order the scenes connected.
The router showing window scene scene, or nil. nil names the primary
while it is unbound: its screens' Mob.Scene.of/1 is nil, even though
list/0 reports the id of native's default scene for it.
Types
@type scene_id() :: String.t() | nil
A window scene id (UISceneSession.persistentIdentifier), nil where there is none.
Functions
Returns a specification to start this module under a supervisor.
See Supervisor.
Every live router as {scene_id, router_pid}: the primary first, then one
per further window scene in the order the scenes connected.
The router showing window scene scene, or nil. nil names the primary
while it is unbound: its screens' Mob.Scene.of/1 is nil, even though
list/0 reports the id of native's default scene for it.
What every window shows: {scene_id, screen_module, screen_pid} per live
router, in list/0 order. A router whose screen is mid-restart is left out.