1475991fd6
M1: PageErrorBoundary wraps the body of every InteractiveServer page; the inert layout boundary is removed. m1: DisposeBridge cancels its CTS when the 5 s wait times out. m2: a CSV import that commits after the modal closed still raises OnSaved. m3: document SecurityMode=None no-fallback in Client.CLI.md / Client.UI.md. m4: browsePageDelayMs written only when loaded or changed; negative flagged. Claude-Session: https://claude.ai/code/session_01Ef2ozxS7QPanGT8uoATbTe
46 lines
2.3 KiB
Plaintext
46 lines
2.3 KiB
Plaintext
@implements IDisposable
|
|
|
|
@* Confines an exception thrown while rendering or handling an event on a page to the page body, instead
|
|
of killing the whole Blazor circuit (the blank "reload" bar).
|
|
|
|
WHERE IT MAY LIVE: this host's <Routes/> is static SSR, so MainLayout renders statically and every
|
|
@rendermode InteractiveServer page is its OWN interactive root, not a child of anything in the layout.
|
|
A boundary in the layout therefore never sees a page failure and its Recover/navigation handlers are
|
|
dead. The boundary has to sit INSIDE the interactive root — i.e. each interactive page wraps its body
|
|
in <PageErrorBoundary>. Pinned by AdminUiFormSweepGuardTests (every interactive page wraps) and
|
|
PageErrorBoundaryTests (the component really contains a throw and recovers).
|
|
|
|
It does NOT catch exceptions from async-void handlers or timer callbacks. *@
|
|
<ErrorBoundary @ref="_boundary">
|
|
<ChildContent>@ChildContent</ChildContent>
|
|
<ErrorContent Context="ex">
|
|
<div class="alert alert-danger m-3" role="alert" data-testid="page-error-boundary">
|
|
<strong>This page hit an unexpected error.</strong>
|
|
<div class="small mono mt-1">@ex.GetType().Name: @ex.Message</div>
|
|
<button type="button" class="btn btn-sm btn-outline-danger mt-2" @onclick="Recover">Try again</button>
|
|
</div>
|
|
</ErrorContent>
|
|
</ErrorBoundary>
|
|
|
|
@code {
|
|
private ErrorBoundary? _boundary;
|
|
|
|
[Parameter] public RenderFragment? ChildContent { get; set; }
|
|
|
|
[Inject] private NavigationManager Nav { get; set; } = default!;
|
|
|
|
protected override void OnInitialized() => Nav.LocationChanged += OnLocationChanged;
|
|
|
|
/// <summary>Clears a tripped boundary and re-renders the wrapped content.</summary>
|
|
public void Recover() => _boundary?.Recover();
|
|
|
|
// A fresh navigation (e.g. same page, new route parameters, where the component instance is reused)
|
|
// clears a tripped boundary so the new location isn't stuck behind the old error. InvokeAsync keeps
|
|
// the Recover on the renderer's dispatcher.
|
|
private void OnLocationChanged(object? sender, Microsoft.AspNetCore.Components.Routing.LocationChangedEventArgs e)
|
|
=> _ = InvokeAsync(Recover);
|
|
|
|
/// <inheritdoc/>
|
|
public void Dispose() => Nav.LocationChanged -= OnLocationChanged;
|
|
}
|