Skip to content

Single-source window creation; drop the windowManager scene-less fallback (follow-up to #25643) #25664

Description

@jkmassel

Follow-up to #25643 (UIScene life-cycle adoption). Non-blocking cleanup — captured here so it isn't lost.

Background

#25643 moved window creation to the first scene connect (WordPressAppDelegate.showInitialUI(in:)). To stay safe if windowManager is touched before any scene has connected (e.g. a background-launch code path), the windowManager lazy getter recovers by asserting and fabricating a window:

if window == nil {
    assertionFailure("windowManager accessed before any scene connected")
    window = UIWindow(frame: UIScreen.main.bounds)
}

showInitialUI(in:) then carries a matching branch whose only job is to adopt that fabricated window instead of orphaning the UI built into it.

Why it's worth revisiting

  • The fabricated window is scene-less (UIWindow(frame:)), which the rest of the new code treats as invalid — WindowManager.displayOverlayingWindow and CompliancePopoverCoordinator assert a scene-attached main window. It's the only UIWindow(frame:) left in the non-test code.
  • The invariant is enforced from both ends — the lazy fabricates, showInitialUI repairs — ~150 lines apart. Removing one without the other silently turns the repair into dead code or reintroduces an orphan-window bug.
  • assertionFailure compiles out in release, so a missed early-access path ships as "works by accident" (an invisible, half-built UI) rather than a signal.
  • Correctness rests on every pre-scene-connect caller of windowManager / RootViewCoordinator.shared being guarded by convention rather than by the type system.

The early-access vector is RootViewCoordinator.shared (a static let) evaluating WordPressAppDelegate.shared?.windowManager at first access and caching it for the process lifetime — so the first touch of RootViewCoordinator.shared / .sharedPresenter is what drags windowManager into existence. Its own windowManager property is already WindowManager?, so the consumer already tolerates nil.

Proposed direction

  1. Single creation site + explicit optional. Make windowManager an assigned WindowManager?, set when the window is built in showInitialUI. Delete the lazy fallback and the existingWindow adoption branch. Early access becomes a nil callers handle (composes with RootViewCoordinator already taking WindowManager?) instead of a fabricated scene-less window. Ripple is a couple of ? at call sites.

  2. Remove the early-access vector (deeper). Stop RootViewCoordinator.shared from eagerly reading windowManager at static-init — resolve it lazily at point-of-use:

    private var windowManager: WindowManager? { WordPressAppDelegate.shared?.windowManager }

    Then nothing reaches windowManager before the scene exists, the fallback is provably dead and can be deleted outright, and the property could even go back to non-optional.

Files

  • WordPress/Classes/System/WordPressAppDelegate.swiftwindowManager lazy getter, showInitialUI(in:)
  • WordPress/Classes/System/Root View/RootViewCoordinator.swiftstatic let shared / sharedPresenter

Surfaced during review of #25643. The PR description already notes that "the separation between the UI and the app process is still not very clean, but that can be tightened in future PRs" — this is part of that.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions