Where a component a [[component …]] marker names, in stored content, is drawn as a small box
when it raises while it draws, and the rest of the page draws around it.
MishkaCmsCore.Runtime.ContentComponents draws every marker of rich_content/1 through draw/3
— a docs note's, a blog post's, any stored content's, a marker inside another marker included —
so a component that raises takes neither the HTTP response nor the visitor's LiveView with it.
A page's, a layout's and an error page's own <.component> calls are not drawn through it: their
author sees the component in the builder's canvas, and they render at full speed.
What it catches
An exception raised while the component's function runs, or while any part of its template is
evaluated: its expressions, its :if and :for, the slots it renders and the function
components it calls.
A HEEx template is evaluated lazily. The %Phoenix.LiveView.Rendered{} a component returns holds
its values in a closure that LiveView calls later — while it writes the HTTP response, or while it
diffs the connected render — and by then nothing of this module is on the stack. So draw/3
calls those closures itself, inside the boundary, and hands LiveView a tree whose closures return
the values already computed. Each closure still runs once, as LiveView would have run it; a
component that does not raise is drawn from the same values, and only the walk that collects them
is added.
A closure is evaluated with change tracking off. When the component is drawn again because its attributes changed, every value it draws is sent again, not only the ones that changed. A component whose attributes did not change is not drawn again at all, as before.
What it does not catch
- An exception that names a response status of its own between 400 and 499 — a
Plug.Exception, such asMishkaCmsCore.ErrorsExceptions.NotFoundErroror an Ash error that answers 403 or 404. That is the page's answer, and it is raised on. - A
throwand anexit. LiveView sends redirects as throws, and an exit belongs to the process drawing the page. - A stateful component. LiveView runs its
update/2andrender/1itself, after the page is drawn; one that raises stops the visitor's LiveView, as before. - A component a page, a layout or an error page names in its own template.
- Content drawn in the canvas and the previews, which draw through
static_component/1and contain a raise themselves.
The box
This component could not be drawn, in a <span data-role="component-crashed"> drawn with the
classes box_class/0 names and no inline style, so a strict CSP allows it. The error is logged
with the component's name, its site and the stacktrace, under an error id. A viewer that
put_viewer/1 names as a dashboard admin also sees the id in the box; a visitor never does.
Events
runtime:component:crashed— after a component's raise is contained, with%{component_name: name, site: site, site_id: site_id, error_id: id, error: exception, stacktrace: stacktrace}.siteis the site's name,nilfor a global component, andsite_idthe site being rendered. Fired withreturn: false: a plugin is told, and its answer is not read.
Summary
Functions
The classes the box is drawn with, for whatever builds a site's stylesheet.
Draws the component render returns, or the box when drawing it raises. A stateful component's
%Phoenix.LiveView.Component{} is returned as it is.
Names the signed-in user of the response this process draws, nil for a guest.
Functions
@spec box_class() :: String.t()
The classes the box is drawn with, for whatever builds a site's stylesheet.
@spec draw(String.t(), String.t() | nil, (-> Phoenix.LiveView.Rendered.t() | Phoenix.LiveView.Component.t())) :: Phoenix.LiveView.Rendered.t() | Phoenix.LiveView.Component.t()
Draws the component render returns, or the box when drawing it raises. A stateful component's
%Phoenix.LiveView.Component{} is returned as it is.
name and site are the component's name and its site's name, nil for a global component;
they are what the log and runtime:component:crashed name.
ComponentBoundary.draw("chelekom-alert", "my-site", fn -> MyComponent.alert(assigns) end)
@spec put_viewer(map() | nil) :: :ok
Names the signed-in user of the response this process draws, nil for a guest.
A dashboard admin named here sees a contained component's error id in its box. The user is kept
for this response only — tied to its CSP nonce — so a process that draws a later response draws
it for nobody until it is named again. Nothing calls it on a page's render path; a host that wants
its admins to see the id calls it where the user is known, after
MishkaCmsCore.LiveUserAuth.load_current_user/1:
socket = LiveUserAuth.load_current_user(socket)
:ok = ComponentBoundary.put_viewer(socket.assigns[:current_user])