The one validated shape every page-builder registry entry takes — an inspector section, a right-rail tab, a Studio tab, a canvas toolbar action, a command-palette command, an audit check, a marketplace source, an AI backend.
§28 lists these as eight separate structs that all carry the same six fields. They are one struct
here, with kind naming which registry an entry belongs to, because the alternative is eight
modules differing only in their moduledoc — and one shape means a plugin author learns the
contract once and Events.collect/2 can sort and de-duplicate every registry with the same code.
render and run are {module, function} tuples or a LiveComponent module, never closures:
a descriptor travels through a MishkaInstaller hook payload, which is plain-map only. This is the
MishkaCmsCore.Dashboard.Widget rule, for the same reason.
Permissions
permissions is a list of permission names where holding any is enough, checked the same way
a dashboard widget's is. An empty list means everyone who can open the builder sees it. A section
a viewer may not see is filtered out of the registry, so it never renders and its events never
fire.
Summary
Functions
A sample %MishkaCmsCore.Builder.Descriptor{} populated from defaults + type-based fallbacks.
A descriptor's de-duplication key. Tolerates a plain map so a plugin that builds its entry by hand
instead of through builder/1 still sorts and de-duplicates rather than crashing the registry.
Every registry a descriptor can belong to.
A descriptor's display label, falling back to its key.
Filters a collected registry down to what this viewer may see.
Whether user may see this descriptor — an empty permission list means everyone, otherwise
holding any one of the listed permissions is enough.
A descriptor's sort priority; anything unreadable sorts last.
Renders a descriptor's component with assigns. A descriptor whose render ref is missing or
raises degrades to nothing rather than taking the panel down — the extension-isolation boundary
the runtime already applies to broken templates.
Types
@type t() :: %MishkaCmsCore.Builder.Descriptor{ config: map() | nil, description: String.t() | nil, group: String.t() | nil, icon: String.t() | nil, key: String.t(), kind: atom(), label: String.t() | nil, permissions: list() | nil, priority: integer() | nil, render: any() | nil, run: any() | nil, shortcut: String.t() | nil }
Functions
A sample %MishkaCmsCore.Builder.Descriptor{} populated from defaults + type-based fallbacks.
A descriptor's de-duplication key. Tolerates a plain map so a plugin that builds its entry by hand
instead of through builder/1 still sorts and de-duplicates rather than crashing the registry.
@spec kinds() :: [atom()]
Every registry a descriptor can belong to.
A descriptor's display label, falling back to its key.
Filters a collected registry down to what this viewer may see.
Whether user may see this descriptor — an empty permission list means everyone, otherwise
holding any one of the listed permissions is enough.
A descriptor's sort priority; anything unreadable sorts last.
@spec render(map(), map()) :: Phoenix.LiveView.Rendered.t() | nil
Renders a descriptor's component with assigns. A descriptor whose render ref is missing or
raises degrades to nothing rather than taking the panel down — the extension-isolation boundary
the runtime already applies to broken templates.