diff --git a/content/guide/multi-window.md b/content/guide/multi-window.md
index 6bf543f7..01c01526 100644
--- a/content/guide/multi-window.md
+++ b/content/guide/multi-window.md
@@ -1,31 +1,52 @@
---
title: Multiple Windows
-description: Develop with multiple windows on supported devices.
+description: Develop with multiple windows on iOS and Android.
contributors:
- NathanWalker
+ - edusperoni
---
-NativeScript 9 adds first-class support for iOS multi-window (multi-scene) applications by adopting the UIScene lifecycle when enabled. This guide explains how to enable scenes, how NativeScript integrates with them, and how to work with scene-specific APIs and events.
+NativeScript 9.1 introduces `NativeWindow`, a cross-platform window API. A single JavaScript context can drive several windows: UIScene-backed windows on iOS (iPadOS Split View and Stage Manager, visionOS) and activity-backed windows on Android (split screen, desktop windowing, foldables).
:::tip Why this matters
-Apple is moving all iOS apps to the UIScene lifecycle. Enabling scenes now makes your app future‑proof and unlocks multiple windows on iPadOS and visionOS.
+Apple is moving all iOS apps to the UIScene lifecycle and will require it in an upcoming release. Adopting scenes now makes your app future-proof and unlocks multiple windows on iPadOS and visionOS.
+:::
+
+## Concepts
+
+There are two objects to keep apart:
+
+- **Application** — the process and the JavaScript context. There is exactly one, no matter how many windows are open. It is the browser.
+- **NativeWindow** — one OS window: a `UIWindowScene` plus its `UIWindow` on iOS, an `Activity` on Android. It is a tab.
+
+Every app registers at least one window — the **primary** window — including apps that never open a second one. Each window carries:
+
+| Property | Description |
+| ----------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| `id` | Stable identifier for the lifetime of the JavaScript context. It survives a detach and re-attach, so it correlates a window across an iOS scene reconnect or an Android activity recreation. |
+| `role` | `application`, `embedded`, `carplay` or `externalDisplay`. |
+| `state` | `attached` (connected to a live native surface), `detached` (the surface went away but the window can be reconnected) or `closed`. |
+| `isPrimary` | At most one window is primary at a time. When the primary window closes, another attached window is promoted. |
+| `rootView` | The view tree this window hosts. |
+
+:::tip Two ways to structure a multi-window app
+Windows share one JavaScript context, so you decide how much else they share. Either treat every window as a fully separate app — bootstrap it with its own services and state — or bootstrap once and give each window another view tree over shared state, closer to modal navigation.
:::
## Supported platforms
-- iPad running iPadOS 13 or later (multi-window capable)
-- visionOS (Vision Pro)
-- iPhone: runs with UIScene lifecycle; multiple windows are not exposed to users, but adopting UIScene is recommended
+- **iPadOS 13+** — Split View, Slide Over and Stage Manager show windows side by side.
+- **visionOS** — multiple windows.
+- **iPhone** — runs the UIScene lifecycle, but the system exposes a single window.
+- **Android** — `Application.openWindow()` is experimental. Each window is an activity in its own task: on a phone the new window covers the current one and both appear in recents, while split screen, desktop windowing or a foldable shows them side by side.
-## Prerequisites
+The per-window APIs and events on this page work everywhere, including single-window apps.
-- NativeScript 9+
-- iOS 13+ runtime
-- Xcode/iOS tooling capable of building with UIScene (Xcode 11+)
+## Enabling multiple windows
-## Enable scene lifecycle (Info.plist)
+### iOS: add the scene manifest
-NativeScript will automatically switch to UIScene lifecycle when a scene manifest is present in your iOS app `Info.plist`. Add the following keys:
+NativeScript switches to the UIScene lifecycle when a scene manifest is present in `App_Resources/iOS/Info.plist`:
```xml
UIApplicationSceneManifest
@@ -34,213 +55,323 @@ NativeScript will automatically switch to UIScene lifecycle when a scene manifes
UIWindowSceneSessionRoleApplication
UIApplicationSupportsMultipleScenes
- UISceneConfigurations
-
- UIWindowSceneSessionRoleApplication
-
-
- UISceneConfigurationName
- Default Configuration
- UISceneDelegateClassName
- SceneDelegate
-
-
-
```
-When this configuration is detected, NativeScript adopts UIScene; on devices that don’t support scenes, your app continues to behave as a single-window app.
+That is the whole manifest. NativeScript installs its scene configuration on your application delegate at runtime, so `UISceneConfigurations` and `UISceneDelegateClassName` no longer have to be declared — apps that still declare them pointing at `SceneDelegate` keep working.
+
+Without the manifest the app runs the pre-scene `UIApplication` lifecycle with a single window, and everything below except opening a second window still applies.
:::tip iPhone and UIScene
-Even on iPhone, adding the manifest switches your app to UIScene lifecycle. Xcode may show warnings like “UIScene lifecycle will soon be required” — using the manifest addresses this.
+Even on iPhone, adding the manifest switches your app to the UIScene lifecycle. Xcode may warn that "UIScene lifecycle will soon be required" — the manifest addresses that.
:::
-## How it works in NativeScript
+### Android: allow a second activity instance
+
+`Application.openWindow()` launches your start activity into its own task, so its `launchMode` has to allow a second instance. The app template ships with:
+
+```xml
+
+```
+
+`singleTask` (the template default before 9.1) and `singleInstance` hand the launch intent to the existing activity's `onNewIntent` instead of creating a window. `singleInstancePerTask` (API 31+) keeps single-task behavior for launcher and deep-link starts while allowing extra windows; `standard` works too.
-When the scene manifest is present:
+:::warning Experimental on Android
+`Application.openWindow()` is experimental on Android. Behavior around OEM recents, task management and window restoration after process death still varies by device.
+:::
-- A `SceneDelegate` (exposed to iOS as `SceneDelegate`) implements `UIWindowSceneDelegate` to integrate scenes with NativeScript’s application runtime.
-- A UIWindow is created per `UIWindowScene` and mapped internally, preserving compatibility with traditional app lifecycle APIs.
-- NativeScript fires scene-specific events and forwards core application lifecycle events from a primary scene to maintain compatibility with existing code.
+## Providing content for a window
-### Scene lifecycle events
+Before 9.1, the first window's UI came from the `launch` event or the application main entry, and a second window had no way to get different content. Those two jobs are now separate:
-Use the `SceneEvents` constants to subscribe to scene lifecycle changes:
+- **`ready`** fires once per JavaScript context, as soon as the context is initialized. It is never deferred, so it also fires on a background launch that opens no window. Do app-level initialization here.
+- **`Application.setWindowContentResolver()`** supplies the UI for each window that needs content.
```ts
-export const SceneEvents = {
- sceneWillConnect: 'sceneWillConnect',
- sceneDidActivate: 'sceneDidActivate',
- sceneWillResignActive: 'sceneWillResignActive',
- sceneWillEnterForeground: 'sceneWillEnterForeground',
- sceneDidEnterBackground: 'sceneDidEnterBackground',
- sceneDidDisconnect: 'sceneDidDisconnect',
- sceneContentSetup: 'sceneContentSetup',
-}
+import { Application } from '@nativescript/core'
+import type { WindowContentRequest } from '@nativescript/core'
+
+Application.on('ready', () => {
+ // app-wide initialization - no window exists yet
+})
+
+Application.setWindowContentResolver((request: WindowContentRequest) => {
+ // Hand the primary window - and anything the system restored on its own -
+ // back to the application main entry.
+ if (request.isPrimary) {
+ return undefined
+ }
+
+ switch (request.data?.kind) {
+ case 'inspector':
+ return 'pages/inspector-page'
+ default:
+ return createDetailPage(request.window)
+ }
+})
+
+Application.run({ moduleName: 'app-root' })
+```
+
+The resolver receives a `WindowContentRequest`:
+
+| Property | Description |
+| ---------------------------------------------- | -------------------------------------------------------------------------------------------------- |
+| `window` | The `NativeWindow` that needs content. |
+| `isPrimary` | Whether this is the application's primary window. |
+| `data` | The payload passed to `openWindow()` — `NSUserActivity.userInfo` on iOS, intent extras on Android. |
+| `ios.connectionOptions` | The scene's `UISceneConnectionOptions`. |
+| `android.intent`, `android.savedInstanceState` | The launch intent and saved state for the activity. |
+
+What you return decides what happens next:
+
+| Return value | Result |
+| ------------------------------------ | ---------------------------------------------------------------------- |
+| A `View` | Becomes the window content. |
+| A `NavigationEntry` or a module name | Built, then used as the window content. |
+| `null` | You take ownership — set the content later with `window.setContent()`. |
+| `undefined` | Falls back to the application main entry. |
+
+Keep the resolver installed for the life of the process: a window that detaches and re-attaches (scene reconnect, activity recreation) asks for its content again, long after the code that installed the resolver may have moved on.
+
+Content can also be set at any time, from anywhere:
+
+```ts
+const window = Application.getWindowById(id)
+window.setContent(new DetailPage())
```
-Event payloads include scene and window references:
+`setContent()` replaces any content set earlier, tears the previous root view down and raises `contentLoaded`. A view that is already the root of another window is released from that window first — the view itself is left intact.
+
+### Startup order
+
+Bringing a window up follows a fixed order you can rely on:
+
+`ready` → `windowOpen` → the raw platform event (`sceneWillConnect` / `activityCreated`) → content resolution → `contentLoaded` → `activate` and `displayed`.
+
+## Opening and closing windows
```ts
-/** iOS event data for UIScene lifecycle (iOS 13+). */
-export interface SceneEventData extends ApplicationEventData {
- /** The UIWindowScene instance associated with this event. */
- scene?: UIWindowScene
- /** The UIWindow for this scene (if applicable). */
- window?: UIWindow
- /** Scene connection options (for sceneWillConnect). */
- connectionOptions?: UISceneConnectionOptions
- /** Additional user info from the notification. */
- userInfo?: NSDictionary
-}
+import { Application } from '@nativescript/core'
+
+Application.openWindow({ data: { kind: 'inspector' } })
```
-### iOSApplication scene APIs
+The `data` payload travels to the new window and arrives as `request.data` in the content resolver — serialized into `NSUserActivity.userInfo` on iOS, added as intent extras on Android.
-When UIScene is active, `Application.ios` exposes helpers for inspecting and controlling scenes and windows:
+Multi-window is a device capability rather than a platform one: an iPhone and an iPad run the same iOS build, but only one of them can show two scenes. Check before offering the option:
-- `supportsScenes(): boolean` — iOS supports UIScene (iOS 13+)
-- `supportsMultipleScenes(): boolean` — app can present multiple scenes/windows (iPadOS; typically false on iPhone and some simulators)
-- `getAllWindows(): UIWindow[]` — all app windows across scenes
-- `getAllScenes(): UIScene[]` — all attached scenes
-- `getWindowScenes(): UIWindowScene[]` — filtered to window scenes
-- `getPrimaryWindow(): UIWindow | undefined` — the primary window (for compatibility)
-- `getPrimaryScene(): UIWindowScene | undefined` — the primary scene
-- `isUsingSceneLifecycle(): boolean` — whether UIScene lifecycle is active
-- `setWindowRootView(window: UIWindow, view: View): void` — set NativeScript root view for a given scene’s window
+```ts
+import { Application, isIOS } from '@nativescript/core'
-## Usage
+const canOpenWindows = isIOS
+ ? Application.ios.supportsScenes() && Application.ios.supportsMultipleScenes()
+ : true
+```
-### Listen to scene events
+Close a window through the window itself:
```ts
-import { Application, SceneEvents } from '@nativescript/core'
+window.close()
+```
-Application.on(SceneEvents.sceneWillConnect, (args) => {
- console.log('Scene connecting:', args.scene)
- console.log('Window:', args.window)
- console.log('Connection options:', args.connectionOptions)
-})
+The primary window refuses to close. A window that does close raises `close` exactly once, is dropped from the registry, and then has every listener on it cleared — so handlers registered on a window never outlive it, and a closed window instance must not be reused.
-Application.on(SceneEvents.sceneDidActivate, (args) => {
- console.log('Scene active:', args.scene)
-})
+## Working with windows
-Application.on(SceneEvents.sceneWillResignActive, (args) => {
- console.log('Scene will resign active:', args.scene)
-})
+| API | Description |
+| ---------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------- |
+| `Application.primaryWindow` | The primary window. |
+| `Application.activeWindow` | The window the user is interacting with — the one that activated most recently and is still attached. Falls back to the primary window. |
+| `Application.getWindows(role?)` | Windows filtered by role, defaulting to the view-carrying roles `application` and `embedded`. Pass `'all'` to enumerate every registered surface. |
+| `Application.getWindowById(id)` | A registered window by id. |
+| `Application.openWindow(options?)` | Opens a new window. |
-Application.on(SceneEvents.sceneWillEnterForeground, (args) => {
- console.log('Scene will enter foreground:', args.scene)
-})
+To go from a view to the window hosting it, use `getNativeWindow()`. It walks up to the root view — through the presenting view of any modal on the way — so a view re-parented into another window reports the window it moved to:
-Application.on(SceneEvents.sceneDidEnterBackground, (args) => {
- console.log('Scene entered background:', args.scene)
-})
+```ts
+import { Frame } from '@nativescript/core'
+import type { EventData, View } from '@nativescript/core'
-Application.on(SceneEvents.sceneDidDisconnect, (args) => {
- console.log('Scene disconnected:', args.scene)
-})
+export function onTap(args: EventData) {
+ const view = args.object as View
-Application.on(SceneEvents.sceneContentSetup, (args) => {
- // Create and attach NativeScript View content for the new scene here
- // See "Provide scene-specific UI" section below
- setupSceneContent(args)
+ // Navigate the frame of the window this button lives in, not the app's topmost frame.
+ Frame.topmost(view.getNativeWindow()).navigate('pages/details')
+}
+```
+
+`Frame.topmost()` takes an optional window and defaults to `Application.activeWindow`.
+
+## Window lifecycle events
+
+Subscribe on a `NativeWindow` for events scoped to that one window. The `NativeWindowEvents` constants carry the names:
+
+| Event | Raised when |
+| --------------------------- | ---------------------------------------------------------------------------------------------------------------------- |
+| `attached` | A native surface is bound to the window — on the first connect and on every re-attach. |
+| `detached` | The native surface goes away while the window session stays alive (iOS scene disconnect, Android activity recreation). |
+| `activate` / `deactivate` | The window gains or loses focus. |
+| `background` / `foreground` | The window enters the background or comes back to the foreground. |
+| `contentLoaded` | The root view content is set or changed. |
+| `displayed` | The window content has been displayed for the first time. |
+| `close` | The window session ends for good. Fires exactly once. |
+| `orientationChanged` | This window's orientation changes. |
+| `systemAppearanceChanged` | This window switches between light and dark. |
+| `layoutDirectionChanged` | This window switches between `ltr` and `rtl`. |
+
+```ts
+import { Application, NativeWindowEvents } from '@nativescript/core'
+
+const window = Application.activeWindow
+
+window.on(NativeWindowEvents.orientationChanged, (args) => {
+ console.log(`${args.window.id} is now ${args.newValue}`)
})
+
+// No unsubscribe needed - the framework drops every listener on a window
+// right after its `close` event.
```
-### Inspect and manage windows
+:::warning detached is not closed
+A scene disconnect or an activity recreation raises `detached`, not `close`. The window stays registered, keeps its id and keeps its listeners, and the same instance is handed back when a surface re-attaches. Only tear down per-window state on `close`.
+:::
+
+`displayed` currently fires for the primary window only.
+
+## Application-level window events
+
+The `WindowEvents` constants cover the registry as a whole:
```ts
-import { Application } from '@nativescript/core'
+import { Application, WindowEvents } from '@nativescript/core'
-if (Application.ios.supportsScenes()) {
- const windows = Application.ios.getAllWindows()
- const scenes = Application.ios.getWindowScenes()
- const primaryWindow = Application.ios.getPrimaryWindow()
+Application.on(WindowEvents.windowOpen, (args) => {
+ console.log(`opened ${args.window.id}`)
+})
+Application.on(WindowEvents.windowClose, (args) => {
+ console.log(`closed ${args.window.id}`)
+})
+Application.on(WindowEvents.primaryWindowChanged, (args) => {
+ console.log(`${args.window.id} is now primary`)
+})
+```
- console.log(`App has ${windows.length} windows`)
- console.log(`App has ${scenes.length} scenes`)
- console.log('Primary window:', primaryWindow)
+Two application events changed meaning when more than one window is involved:
- if (Application.ios.isUsingSceneLifecycle()) {
- console.log('Using UIScene lifecycle')
- }
-} else {
- console.log('Single-window app lifecycle in effect')
-}
+- **`suspend` / `resume`** reflect whole-app state — they are raised once the app itself leaves or returns to the foreground, not when an individual window backgrounds. Use a window's `background` and `foreground` events to track one window.
+- **`exit`** is raised on Android when the _last_ window finishes (the process may stay alive); on iOS it still means process termination.
+
+## Per-window orientation, appearance and direction
+
+Two windows can legitimately disagree about all three, so each window reports its own:
+
+```ts
+window.orientation() // 'portrait' | 'landscape' | 'unknown'
+window.systemAppearance() // 'light' | 'dark' | null
+window.layoutDirection() // 'ltr' | 'rtl' | null
```
-### Provide scene-specific UI
+Values are read from the native surface while the window is attached; a detached window reports the last value it saw. A read that catches a change the platform has not reported yet also raises the matching `*Changed` event, so a change is never swallowed.
+
+The CSS classes follow the same rule. `ns-portrait` / `ns-landscape`, `ns-light` / `ns-dark` and `ns-ltr` / `ns-rtl` are applied to each window's root view and to the modals presented over it, instead of living in the process-wide system class list. `CSSUtils.getRootViewCssClasses()` no longer returns them.
+
+## Reaching the native window
```ts
-import { Application, Page, Utils } from '@nativescript/core'
+const uiWindow = window.ios?.uiWindow // UIWindow
+const scene = window.ios?.scene // UIWindowScene, absent on the pre-scene lifecycle
+const activity = window.android?.activity // AppCompatActivity
+```
-function createPageForScene(scene: UIWindowScene, window: UIWindow): Page {
- // Construct any NativeScript view hierarchy here
- const page = new Page()
- // ... add content
- return page
-}
+The platform bridges on `Application` are aggregate APIs: they fire for every window, and `args.window` identifies which one.
-export function setupSceneContent(args: SceneEventData) {
- // Optionally distinguish scenes by an id when opening a new window
- // (e.g., via NSUserActivity userInfo)
- let nsViewId: string | undefined
- if (args.connectionOptions?.userActivities?.count > 0) {
- const activity =
- args.connectionOptions.userActivities.allObjects.objectAtIndex(
- 0,
- ) as NSUserActivity
- nsViewId = Utils.dataDeserialize(activity.userInfo).id
- }
+```ts
+Application.ios.on('sceneWillConnect', (args) => {
+ // args.window is the NativeWindow, args.scene the UIWindowScene,
+ // args.uiWindow the native UIWindow
+})
- let page: Page
- switch (nsViewId) {
- case 'newSceneBasic':
- page = createPageForScene(args.scene, args.window)
- break
- case 'newSceneAlt':
- page = createPageForScene(args.scene, args.window) // replace with alt page
- break
- default:
- page = createPageForScene(args.scene, args.window)
- }
+Application.android.on('activityResult', (args) => {
+ // args.window is the NativeWindow the activity belongs to
+})
+```
- Application.ios.setWindowRootView(args.window, page)
-}
+Every one of these events is also available on a single `NativeWindow`, which is usually what you want in a multi-window app:
+
+```ts
+window.on('activityResult', (args) => {
+ /* only this window's results */
+})
```
-## Custom SceneDelegate (advanced)
+## Advanced: other scene roles on iOS
+
+NativeScript auto-manages scenes with the `UIWindowSceneSessionRoleApplication` role. To handle another role — CarPlay, an external display — return a configuration from `Application.ios.onSceneConfiguration`:
-NativeScript ships a default `SceneDelegate` that integrates UIScene with the runtime and event system. If your application needs custom scene delegate behavior, you can provide your own implementation named `SceneDelegate` in your app and wire additional logic. Ensure that you continue to create a `UIWindow` per `UIWindowScene` and set the NativeScript root view to keep app behavior consistent. Most apps should prefer the default delegate.
+```ts
+Application.ios.onSceneConfiguration = (app, session, options) => {
+ if (session.role === CPTemplateApplicationSceneSessionRoleApplication) {
+ const config = UISceneConfiguration.configurationWithNameSessionRole(
+ 'CarPlay',
+ session.role,
+ )
+ config.delegateClass = MyCarPlaySceneDelegate
+ return config
+ }
-## Compatibility and behavior
+ // Let NativeScript handle everything else.
+ return null
+}
+```
-- Backwards compatibility: on devices or builds without a scene manifest, the traditional single-window lifecycle is used and existing apps continue to work unchanged.
-- Primary scene: for compatibility, NativeScript forwards core app lifecycle events (e.g., didBecomeActive) from the primary scene.
-- Multiple scenes: `supportsMultipleScenes()` is typically only true on physical iPadOS devices; it may return false on iPhone and some simulators.
+If your own application delegate implements `applicationConfigurationForConnectingSceneSessionOptions` or `applicationDidDiscardSceneSessions`, NativeScript will not install its defaults over yours — forward to them so window bookkeeping stays correct:
-## Migration guidance
+```ts
+applicationConfigurationForConnectingSceneSessionOptions(app, session, options) {
+ if (session.role === myCustomRole) {
+ return myConfig
+ }
+ return Application.ios.defaultSceneConfiguration(app, session, options)
+}
+```
-Existing apps do not need to change code to adopt UIScene. To enable multi-window capabilities and Scene events:
+## Migrating from NativeScript 9.0
-1. Add the scene manifest to `Info.plist` (see above).
-2. Listen to `SceneEvents` to tailor behavior per window.
-3. If you open additional windows, set their root views with `Application.ios.setWindowRootView` during `sceneContentSetup`.
+Existing single-window apps keep working without changes. If you used the 9.0 scene APIs, the pattern of listening for `sceneContentSetup` and calling `Application.ios.setWindowRootView()` is replaced by the content resolver and `window.setContent()`, which work the same way on both platforms.
-## Troubleshooting
+| 9.0 | 9.1 |
+| ----------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------- |
+| `NativeWindow.iosWindow`, and its `window` property | `window.ios?.uiWindow` and `window.ios?.scene` |
+| `NativeWindow.androidWindow` | `window.android?.activity` |
+| `SceneEventData.window` was the native `UIWindow` | `window` is now the `NativeWindow`; the native window moved to `uiWindow`. A `window` payload key always means a `NativeWindow`. |
+| `getWindows()` returned every surface | Role-filtered, defaulting to `application` + `embedded`. Use `getWindows('all')` for every registered surface. |
+| Android `exit` fired for any finishing activity | Fires only when the last window finishes. iOS `exit` is unchanged. |
+| `suspend` / `resume` tracked the single window | Reflect whole-app state. Use a window's `background` / `foreground` for one window. |
+| Scene disconnect and activity recreation destroyed the window | They raise `detached`; the window stays registered and its listeners survive. |
+| Listeners on a window outlived it | A window clears its listeners after `close`. Do not reuse a closed window instance. |
+| `CSSUtils.getRootViewCssClasses()` included orientation, appearance and direction classes | These are per-window; read them from the window. |
-- “UIScene lifecycle will soon be required” in Xcode: add the scene manifest to `Info.plist` to adopt UIScene.
-- No multiple windows on iPhone: expected; iPhone uses UIScene lifecycle but doesn’t expose multi-window UX to users.
-- `supportsMultipleScenes()` returns false on simulator: test on a physical iPad where multi-window is supported.
+Deprecated but still working:
-## Summary
+| Deprecated | Use instead |
+| --------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------- |
+| The `launch` event | `ready` plus `Application.setWindowContentResolver()` |
+| `SceneEvents.sceneContentSetup` | The `windowOpen` event and `window.setContent()` |
+| `SceneEvents` | `NativeWindowEvents` |
+| `Application.ios.shouldDelayLaunchEvent` | Now a no-op |
+| `Application.orientation()`, `systemAppearance()`, `layoutDirection()` | The equivalent method on a window; they delegate to `primaryWindow` |
+| `getAllWindows()`, `getAllScenes()`, `getWindowScenes()`, `getPrimaryWindow()`, `getPrimaryScene()` | `Application.getWindows()`, `Application.primaryWindow` |
-With UIScene enabled, NativeScript gives you:
+The `activity*` and `scene*` bridges on `Application` are **not** deprecated — they are permanent aggregate APIs that fire for every window.
-- Scene-aware events for window lifecycle handling
-- APIs to inspect scenes and windows and set scene-specific root views
-- Backwards-compatible behavior for apps that haven’t yet adopted scenes
+## Troubleshooting
-Use the examples above as a starting point to build multi-window workflows on iPadOS and visionOS while keeping your app ready for the future UIScene requirement on iOS.
+- **"UIScene lifecycle will soon be required" in Xcode** — add the scene manifest to `Info.plist`.
+- **`supportsMultipleScenes()` returns false** — the device allows a single scene at a time. Either it is an iPhone, or `UIApplicationSupportsMultipleScenes` is missing from `Info.plist`. Test on an iPad.
+- **`openWindow()` on Android brings the existing window forward instead of opening one** — the start activity's `launchMode` is `singleTask` or `singleInstance`. Switch to `singleInstancePerTask` or `standard`.
+- **A second window shows the main entry instead of your UI** — the content resolver returned `undefined`, or the payload you branched on did not arrive. Log `request.data` to check what the platform carried across.
+- **`exit` never fires on Android** — a detached window that never re-attaches keeps the registry non-empty and suppresses `exit`. Suppression is intentional for the ordinary recreation case, and there is no clean way to tell a window that is gone for good from one that is about to re-attach.