MishkaCmsCore. Runtime. Resources. Validations. StatefulPlacement
(Mishka CMS Core v0.0.1)
Copy Markdown
View Source
Refuses markup that places a live component somewhere a live component cannot live.
A stateful component compiles to a Phoenix.LiveComponent, and calling one produces a
%Phoenix.LiveView.Component{} — a token that only a LiveView's diff knows how to turn into
HTML. Three of the four things this runtime compiles are rendered somewhere no such diff runs, and
each failure is worse than an error message:
- An error page is served by a CONTROLLER.
MishkaCmsWeb.ErrorHTML.render/2callspage_module.render(conn, status)and flattens the result, so the token raises — inside that function's own rescue, which falls back to Phoenix's plain default. The site's custom error page is replaced for every visitor, with a warning in the log and nothing on the screen. - A layout wraps error pages as well as pages, so it inherits that.
- Another component's template is rendered by the builder's canvas through
CanvasLive.contain/3, which forces iodata on purpose so a component's raise costs the component and not the author's unsaved session. A live component reached from inside another component's markup builds the token anyway and blows up the PARENT's containment — a red chip on a component the author did not touch.
Where it is enforced
At the action boundary, on the three resources that have somewhere to put markup, so the builder, the Gervaz form, the JSON:API, a seed, a kit installer and a version restore all meet the same rule. A page is the one surface that is a LiveView from end to end, so a page is not checked.
The rule has two ends and both are held. Markup that names a component already live is refused when the markup is written; a component that becomes live while something already names it is refused when IT is written — created live, switched to live, renamed or moved to another site while live, or unarchived while live — with the documents that name it in the message. An archived document is one of those documents: unarchiving it writes none of its markup, so nothing would read that markup then.
Every field that is compiled with the live component/1 in reach
- a layout —
templateandhelpers[].code - an error page — each
error_pages[].template, eacherror_pages[].layout.template,root_layout_templateandhelpers[].code - a component —
template,body,helpers[].code, and inextrathepreludeand eachclauses[].templateandclauses[].body
How a component is found in markup
By reading each field in the language it is compiled in, never by a pattern over the source.
HEEx is parsed. MishkaCmsCore.Runtime.PageBuilder.Tree.parse/1 is the same reader the editor
uses, so a component_name inside a comment or a code sample is not a placement and a name built
at runtime — component_name={@which} — is not a literal this can resolve. The template's own
Elixir is read as well, because {component(%{component_name: "x"})} reaches the same function.
Markup that will not parse is left alone: Validations.TemplateCompiles is what refuses that, and
two messages for one mistake is worse than one.
Elixir — a helper, a body, a prelude — is quoted, and a component call with a literal name is a
placement, as is anything its ~H sigils place.
root_layout_template is classic EEx, compiled by Phoenix.HTML.Engine rather than the tag
engine, so a <.component> tag in it is TEXT that reaches the visitor as written and calls
nothing. What reaches the live function from there is an expression — <%= component(…) %> —
and that is what is read.
Only what this write changed
A record whose stored markup predates this validation stays editable. Refusing every other field on it would lock the author out of the one screen the markup gets fixed in.