# `MishkaCmsCore.Builder.Descriptor`
[🔗](https://github.com/mishka-group/mishka_cms_core/blob/v0.0.1/lib/mishka_cms_core/builder/descriptor.ex#L5)

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.

# `t`

```elixir
@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
}
```

# `builder`

# `enforce_keys`

# `enforce_keys`

# `example`

A sample %MishkaCmsCore.Builder.Descriptor{} populated from defaults + type-based fallbacks.

# `key`

```elixir
@spec key(map()) :: String.t()
```

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.

# `keys`

# `keys`

# `kinds`

```elixir
@spec kinds() :: [atom()]
```

Every registry a descriptor can belong to.

# `label`

```elixir
@spec label(map()) :: String.t()
```

A descriptor's display label, falling back to its key.

# `permitted`

```elixir
@spec permitted(list(), map() | nil) :: list()
```

Filters a collected registry down to what this viewer may see.

# `permitted?`

```elixir
@spec permitted?(map(), map() | nil) :: boolean()
```

Whether `user` may see this descriptor — an empty permission list means everyone, otherwise
holding any one of the listed permissions is enough.

# `priority`

```elixir
@spec priority(map()) :: integer()
```

A descriptor's sort priority; anything unreadable sorts last.

# `render`

```elixir
@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.

---

*Consult [api-reference.md](api-reference.md) for complete listing*
