diff --git a/BlueprintUIAccessibilityCore/Sources/AccessibilityDeferral.swift b/BlueprintUIAccessibilityCore/Sources/AccessibilityDeferral.swift index faa52870c..7ddb06d40 100644 --- a/BlueprintUIAccessibilityCore/Sources/AccessibilityDeferral.swift +++ b/BlueprintUIAccessibilityCore/Sources/AccessibilityDeferral.swift @@ -226,8 +226,11 @@ extension AccessibilityDeferral { sources.forEach { $0.accessibilityElementsHidden = false } + // During a reentrant Blueprint update (e.g. a forced layoutBelowIfNeeded mid-update), + // this container can transiently observe both the outgoing and incoming Receiver before + // the hierarchy settles. That isn't a misconfiguration, so we recover and bail; the + // settled layout pass re-runs updateAccessibility() with the single surviving Receiver. guard receivers.count <= 1 else { - assertionFailure("AccessibilityDeferral.ParentContainer must contain at most one Receiver; found \(receivers.count).") receivers.forEach { $0.apply(content: nil, frameProvider: nil) } return } diff --git a/CHANGELOG.md b/CHANGELOG.md index b1a74fbcb..5b4c3c21f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Fixed +- `AccessibilityDeferral` no longer aborts debug builds when its container transiently observes more than one `Receiver`. The `assertionFailure` treated more than one `Receiver` as a misconfiguration, but that state became reachable once `BlueprintView` began deferring reentrant updates (#618) instead of crashing on them: a reentrant layout pass can now run while the hierarchy is mid-swap, with the outgoing and incoming `Receiver` both briefly present. Previously that reentrancy tripped `BlueprintView`'s precondition first, so the deferral container never reached this state. The assertion was removed in favor of the existing safe recovery (clearing receiver content and bailing); the settled layout pass re-applies content with the single surviving `Receiver`. + ### Added ### Removed