Mob.Scenes (mob v0.9.27)

Copy Markdown View Source

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_screen and 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 asks mob_nif:scenes/0 when 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_screen moves 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.

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.

Types

scene_id()

@type scene_id() :: String.t() | nil

A window scene id (UISceneSession.persistentIdentifier), nil where there is none.

Functions

child_spec(init_arg)

Returns a specification to start this module under a supervisor.

See Supervisor.

list()

@spec list() :: [{scene_id(), pid()}]

Every live router as {scene_id, router_pid}: the primary first, then one per further window scene in the order the scenes connected.

router(scene)

@spec router(scene_id()) :: pid() | nil

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.

screens()

@spec screens() :: [{scene_id(), module(), pid()}]

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.