diff --git a/third-party/nsh/crates/nsh-core/src/element/flow.rs b/third-party/nsh/crates/nsh-core/src/element/flow.rs index bbd15cef9c..1d33763684 100644 --- a/third-party/nsh/crates/nsh-core/src/element/flow.rs +++ b/third-party/nsh/crates/nsh-core/src/element/flow.rs @@ -125,16 +125,19 @@ impl Region { let error = failed.get(); // The view is under the boundary, the fallback is not // (what fails in it goes to the boundary above). - if error.is_none() { - super::reactive::register_boundary(failed); - } - // The run's owner is under the binding, where the - // boundary is: `boundary_of` finds it from there. + let boundary = error + .is_none() + .then(|| super::reactive::register_boundary(failed)); let owner = run_owner(); let elements = untracked(|| { - owner.scope(|| match error { - Some(error) => fallback(error, Reset(failed)), - None => view(), + owner.scope(|| { + if let Some(boundary) = boundary { + super::reactive::provide_context(boundary); + } + match error { + Some(error) => fallback(error, Reset(failed)), + None => view(), + } }) }) .into_iter() diff --git a/third-party/nsh/crates/nsh-core/src/element/reactive.rs b/third-party/nsh/crates/nsh-core/src/element/reactive.rs index 4f390d8cdb..cca54a0a0a 100644 --- a/third-party/nsh/crates/nsh-core/src/element/reactive.rs +++ b/third-party/nsh/crates/nsh-core/src/element/reactive.rs @@ -66,24 +66,38 @@ pub(crate) struct RustData { boundaries: rustc_hash::FxHashMap>>, } -/// Makes the current owner an error boundary: what fails under it sets -/// `failed`, for as long as it lives. -pub(crate) fn register_boundary(failed: Signal>) { +/// The binding that owns a boundary, inherited by its content. +#[derive(Clone, Copy)] +pub(super) struct BoundaryScope(Owner); + +/// Makes the current binding catch its own failures and those of its content. +pub(super) fn register_boundary(failed: Signal>) -> BoundaryScope { let owner = Owner::current(); with_data(|d| d.boundaries.insert(owner, failed)); cleanup(move || { with_data(|d| d.boundaries.remove(&owner)); }); + BoundaryScope(owner) } /// The nearest error boundary at or above `owner`. pub(crate) fn boundary_of(owner: Owner) -> Option>> { - let mut at = Some(owner); - while let Some(owner) = at.filter(Owner::is_live) { - if let Some(signal) = with_data(|d| d.boundaries.get(&owner).copied()) { - return Some(signal); + let handle = owner.0.filter(|_| owner.is_live())?; + let graph = graph(); + let boundaries = &graph.data.boundaries; + if let Some(&failed) = boundaries.get(&owner) { + return Some(failed); + } + // Placed children can outlive one run of the boundary's content. + // Their context still names the binding that catches their failures. + let mut frame = graph.context_of(handle.id); + while let Some(context) = frame { + if let Some(scope) = context.data.1.downcast_ref::() + && let Some(&failed) = boundaries.get(&scope.0) + { + return Some(failed); } - at = owner.parent(); + frame = context.parent.clone(); } None }