# `MishkaCmsCore.UiKit.Contract`
[🔗](https://github.com/mishka-group/mishka_cms_core/blob/v0.0.1/lib/mishka_cms_core/ui_kit/contract.ex#L5)

What a component in a `mishka.ui_kit.bundle.v3` file has to look like before this CMS will install
it.

A bundle is a THIRD PARTY's JSON — Chelekom's today, a headless kit's or somebody's own tomorrow —
and until this module the only check was that `components` was a list. Everything past that was
trusted, so a component missing its template, or shipping `attrs` as an object instead of an array,
travelled all the way into `Ash.create!` and raised there. From the installer that is a failed
install; from the Studio's Install button it took the editor's LiveView down with it.

The contract is deliberately about SHAPE, not content: names, types, and which fields have to be
present for the rest of the pipeline (`UiKitCatalog`, `Inspector`, `PaletteEntry`, the runtime
compiler) to mean anything. A kit is free to add fields this CMS has never heard of —
`UiKitHandler` drops those at the Ash boundary — and free to leave optional ones out.

## What happens to a component that fails

It is skipped and named, exactly as an unreadable bundle file already is. One malformed component
in a kit of 136 should cost that component, not the kit.

# `t`

```elixir
@type t() :: %MishkaCmsCore.UiKit.Contract{
  attrs: list() | nil,
  dependencies: list() | nil,
  extra: map() | nil,
  helpers: list() | nil,
  name: String.t() | nil,
  precompile: boolean() | nil,
  required: boolean() | nil,
  slots: list() | nil,
  stateful: boolean() | nil,
  template: String.t() | nil,
  type: String.t() | nil
}
```

# `builder`

# `check`

```elixir
@spec check(map()) :: :ok | {:error, term()}
```

Checks one component from a bundle.

Returns the reason as a short atom-or-tuple rather than GuardedStruct's own error shape, because
the caller writes it into an installer log line, not a form.

# `enforce_keys`

# `enforce_keys`

# `example`

A sample %MishkaCmsCore.UiKit.Contract{} populated from defaults + type-based fallbacks.

# `fields`

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

The fields the contract governs, for anyone documenting or testing the boundary.

# `keys`

# `keys`

# `partition`

```elixir
@spec partition([map()]) :: {[map()], [{String.t() | nil, term()}]}
```

Splits a bundle's components into the ones this CMS can install and the ones it cannot.

Returns `{kept, rejected}` where `rejected` is `[{name_or_nil, reason}]` — enough for the caller to
say which components were skipped and why.

---

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