Skip to content

feat(swiftui): support container-installed content decorators - #399

Draft
robmaceachern wants to merge 7 commits into
mainfrom
codex/mrkt-142-host-content-decorator
Draft

robmaceachern wants to merge 7 commits into
mainfrom
codex/mrkt-142-host-content-decorator

Conversation

@robmaceachern

@robmaceachern robmaceachern commented Sep 9, 2026

Copy link
Copy Markdown
Member

Summary

Add a pre-load SwiftUI content decoration hook and an explicit designated-content lifecycle for containers. These let a navigation container install preference collection and environment bridging through presentation wrappers while preserving ordinary ObservableScreen conformances and hosting customization hooks.

This framework change belongs to MRKT-142. It is stacked on the independently landable SwiftFormat prerequisite #400; the formatter change is excluded from this PR's target-to-head diff. Keep this PR draft until Rob manually examines it and explicitly approves conversion. No automatic readiness conversion or merge.

How it works

  • UIViewController.decorateObservableScreenContent(with:) installs one ViewModifier around a standard ObservableScreen host's complete content, before its view loads. It checks the actual built controller, including after screen type erasure. ObservableScreenContentDecorationResult distinguishes installation, unsupported hosts, already-loaded views, and an existing decorator. Rejected installations leave the host unchanged.
  • Decoration remains fixed for the host's lifetime. Model updates, local SwiftUI identity, preferred sizing, and original controller customization hooks remain attached to the original host. The undecorated path retains its concrete content type.
  • ScreenContentProviding lets an existing wrapper designate one base child through a retained ScreenContentLifecycle. This is explicit opt-in, not child-hierarchy traversal, presentation-target discovery, or selected-entry forwarding. Modal/toast overlays and nested navigation stacks remain separate integration boundaries.
  • ScreenContentLifecycle.observe(prepare:) prepares the current child immediately and returns a retained observation. Every old integration retires before any replacement is prepared; removal, observation release, and lifecycle destruction also retire it. Replacing with the same controller does nothing, and appearance changes do not end ownership. Access is main-thread-only; preparation and retirement cannot recursively replace the same lifecycle.
  • update(child:with:in:prepareReplacement:onChange:) gains a defaulted preparation callback, invoked only for a newly built replacement before this helper adds containment or loads the view. Custom description closures may already have loaded their controller, so integrations must check installation requirements.
  • CI initializes Xcode and CoreSimulator in the runner's session before downloading older iOS runtimes, addressing the service-initialization failure described in actions/runner-images#12862. The iOS 16.4/17.5/26.2 matrix and failure propagation remain intact.

Validation

  • Xcode 26.5, iOS 26.5: xcodebuild test with the UnitTests scheme and the full WorkflowUI-Tests and WorkflowSwiftUI-Tests targets passed 104 tests with one existing expected failure and zero unexpected failures, after integrating the formatter prerequisite.
  • Coverage includes designated-content retirement/preparation ordering, same-controller updates, released observations, lifecycle destruction, pre-containment replacement preparation, decoration rejection, erased screens, stable SwiftUI identity, environment/model updates, child-host isolation, controller customization, and preferred sizing with and without decoration.
  • mise exec -- swiftformat --lint . --cache ignore: 0/285 files require formatting, 22 skipped. git diff --check passes; normal commit hooks pass.
  • Earlier decoration-only validation passed the full WorkflowSwiftUI suite on iPhone/iOS 17.5, iPad/iOS 17.5, and iPhone/iOS 26.5. That evidence predates the designated-content lifecycle addition; CI on the newly published head is required to qualify the complete supported-OS matrix.
  • No new local golden-image, manual UI, or VoiceOver pass is claimed.

Risk

This adds a public lifecycle contract to shared hosting code. The one-time, pre-load decorator rule protects rendered content identity; designated-child opt-in and synchronous retirement constrain ownership. Custom wrappers must announce replacements at the documented point, and callers must retain observations for the intended integration lifetime. Multiple independent decorators are deliberately unsupported.

Checklist

  • Unit Tests
  • UI Tests
  • Snapshot Tests (iOS only)
  • I have made corresponding changes to the documentation

This response was drafted with AI assistance.

Run Xcode first-launch setup and establish the runner session before downloading simulator runtimes. Keep all existing OS coverage and let installation errors fail the job.

---
*This response was drafted with AI assistance.*
Support Swift 6.2 raw identifiers with a pinned formatter and preserve existing formatting policy. Keep this MRKT-142 supporting tooling change independent of navigation API work.

---
*This response was drafted with AI assistance.*
Keep the SwiftFormat upgrade independently reviewable in #400 while validating the MRKT-142 lifecycle API above that prerequisite.

---
*This response was drafted with AI assistance.*

* origin/codex/mrkt-142-swiftformat-0587:
  build: upgrade SwiftFormat to 0.58.7
Prepare designated wrapper content before containment and retire integrations before replacement. Report explicit ObservableScreen decoration outcomes while preserving original hosting behavior. Cover replacement ordering, observation retirement, and controller preparation in MRKT-142 tests.

Validation: WorkflowUI and WorkflowSwiftUI hosted suites passed 104 tests with one existing expected failure on iOS 26.5; full SwiftFormat lint passed.

---
*This response was drafted with AI assistance.*
@robmaceachern
robmaceachern changed the base branch from main to codex/mrkt-142-swiftformat-0587 September 10, 2026 18:10
Base automatically changed from codex/mrkt-142-swiftformat-0587 to main September 10, 2026 21:03
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant