# `MishkaCmsCore.Runtime.Authoring.Containment`
[🔗](https://github.com/mishka-group/mishka_cms_core/blob/v0.0.5-alpha.4/lib/mishka_cms_core/runtime/authoring/containment.ex#L5)

The one authority on "may this node go inside that one" — the HTML content model plus the
component/slot rules, in Elixir, on the server.

The React editor spread this over four competing lists in three files and only ever consulted them
in the browser, so a tampered payload (or the structure tree, which used a different list than the
canvas) could build markup no parser would accept. Here the client gets a **hint** — an `accepts`
map precomputed per node for instant drag feedback — and the server re-checks every insert and
every move before it mutates anything. Client hints, server decides (ADR-6).

## The rules

  * **Void elements** (`br`, `img`, `input`, …) take no children at all.
  * **Raw text elements** (`textarea`, `title`, `script`, `style`) take text and expressions
    only. A browser reads an element written inside one as text and shows its markup.
  * **Phrasing-only elements** (`span`, `p`, `h1`–`h6`, `button`, `pre`, …) take text and
    other phrasing content, never a block element, and phrasing content is the HTML standard's
    list of it: a `<br>`, an `<img>`, an `<input>`, an `<svg>`, a `<select>`, a `<del>`, a `<button>`,
    a `<label>`, a link and a custom element (a name with a hyphen) belong in a `<p>`, a `<div>`, a
    `<ul>`, a `<section>`, a form and a kit card do not. A paragraph, a heading and the other text
    blocks hold phrasing content without BEING it, so none of them goes inside another one.
  * **A link is transparent**, as HTML's `<a>` is: it holds what its parent may hold. In a
    `<div>` or at the top of a page it takes a heading and a paragraph — a card that is one link —
    and in a `<p>`, a heading or a `<span>` it takes inline content only. The nearest ancestor
    that is not a link decides. Arriving, a link is a block when it holds one.
  * **Structural pairs** the spec fixes exactly: a `table` section takes only `tr`, a `tr` takes
    only `td`/`th`, a list takes only `li`, a `dl` takes `dt`/`dd`/`div`, a `select` takes
    `option`/`optgroup`.
  * **Anchors** may not contain another interactive element — nested interactive content is
    invalid and unusable with a keyboard. A link and a button refuse one at any depth, which is
    HTML's rule for both; the other interactive elements are asked as the target only.
  * **Components and slots** accept children when their metadata declares a slot (or when they
    already hold content, which means a previous author put something there legitimately). A
    dotted tag the kit catalog has never heard of is asked of the compiled module instead, so the
    CMS's own `<.form>` and `<.input>` are containers here the same way they are on the page.
  * **A component is the element its own markup opens with** for the phrasing and landmark
    rules: a kit navbar whose markup is one `<nav>` is a `<nav>`, a kit heading is a heading. The
    CMS's own function components have no markup in the catalog and are the element they render:
    a `<.form>` is a `<form>`, an `<.input>` the `<div>` around its control, a `<.link>` an `<a>`,
    so neither a form nor a field goes inside a `<p>`. A kit component whose markup opens with
    another component is the element that one renders: a kit form wrapper opening with `<.form>`
    is a `<form>`. A component whose markup opens with something else, or that has no markup to
    read, stays opaque and is let through as before.
  * **A form may not go inside a form**, and nothing HTML forbids at silently. What counts as a
    form is `MishkaCmsCore.Runtime.Authoring.Fields.form_node?/2`, the same answer the insert
    funnel and the audit rail take, so a kit's own form wrapper is one here too. The thing being
    dragged is asked about with everything under it, because a Block opens with a band tag and
    carries its form as a child.
  * **Landmarks keep out of each other**: a `<header>` or a `<footer>` holds no header, footer or
    `<main>`, and a `<main>` goes in no article, aside, header, footer or nav — HTML's own rules.
    A `<nav>` holds no header, footer or main either, which is stricter than the spec and is what
    a page footer clicked in while the navbar was selected needs. Asked of every ancestor, like
    the form rule.
  * **A table's own parts go only where a table holds them**: a cell only in a row, a row only in
    a table or one of its sections, a section, a caption or a column group only in a table — the
    parents that list them as their only children, read the other way. A kit component is the
    part its markup opens with, and a kit component holding one is the element its markup renders
    its content in (a kit table's `<tbody>`). Not at the top of a page either; a component's own
    markup may open with one (`fragment?: true`).

Every refusal carries a sentence written for the person dragging, which the canvas shows as the
demo's danger toast near the drop point. Markup that was typed rather than dropped is asked with
`misplaced/2`, which reuses three of these rules for a whole document.

A field that lands with no form around it is not refused: see
`MishkaCmsCore.Runtime.Authoring.PageForms.FormPlacement`, which wraps it instead. Building a
form top-down is an ordinary gesture, so only the nesting HTML itself forbids is an error here.

# `accepts_children?`

```elixir
@spec accepts_children?(String.t() | nil) :: boolean()
```

Whether a bare HTML tag can hold children **at all** — false only for void elements.

This is deliberately narrower than the React `canAcceptChild` it replaces. That one also answered
false for every phrasing-only tag, and its single caller used the answer as "accepts any child",
so the editor refused to drop even a word of text into a `<span>` or an `<a>`. Whether a *block*
child is allowed is a separate question, asked per-pair by `can_drop?/4`.

Components and slots answer `true`; whether they really have somewhere to put a child depends on
their slot metadata, which `can_drop?/4` checks.

# `accepts_map`

```elixir
@spec accepts_map(
  MishkaCmsCore.Runtime.Authoring.Tree.t(),
  MishkaCmsCore.Runtime.Authoring.Tree.Node.t() | map(),
  keyword()
) :: %{required(String.t()) =&gt; boolean()}
```

Precomputes, for every node in the tree, whether `dragged` may land inside it — the hint map the
drag hook uses to paint drop targets without a round trip per hover. The server still re-checks
the drop it is actually asked to perform.

An editor asks for it at `dragstart`; the visual editor (`mishka_cms_builder`) also folds in its
session's locks. A refusal here is not the end of the gesture there: it places the node beside the
target instead.

# `can_drop?`

```elixir
@spec can_drop?(
  MishkaCmsCore.Runtime.Authoring.Tree.t(),
  MishkaCmsCore.Runtime.Authoring.Tree.Node.t() | map(),
  MishkaCmsCore.Runtime.Authoring.Tree.parent(),
  keyword()
) :: :ok | {:error, String.t()}
```

Whether `dragged` may be dropped into `target`.

`dragged` is a `Tree.Node`, or a palette entry — any map carrying `:tag` (what it will become) and
optionally `:slots`. `target` is a node id or `:root`.

Options:

  * `:components` — the palette metadata list (maps with `name` and `slots`), used to decide
    whether a component declares somewhere to put a child and what part it plays in a form.
  * `:dragged_tree` — the tree `dragged` belongs to, which for an insert is the parsed template
    rather than the document. Without it a Block's own `<.form>` is invisible here, because a
    block opens with a band tag and only its head node arrives.
  * `:roles` — the memo `MishkaCmsCore.Runtime.Authoring.Fields.roles/2` builds, computed once
    per drag by `accepts_map/3` and rebuilt here when a caller asks about a single drop.
  * `:outer_tags` — the element each component's markup opens with, by component name; built
    once per drag by `accepts_map/3`, and read off `:components` as needed for a single drop.
  * `:fragment?` — `true` when the tree is a fragment placed somewhere else later (a component's
    own markup), so its top level may hold a table part. A page's or a layout's may not.

Returns `:ok` or `{:error, message}` with a sentence meant for the person dragging.

# `can_hold?`

```elixir
@spec can_hold?(
  MishkaCmsCore.Runtime.Authoring.Tree.Node.t() | map(),
  String.t(),
  keyword()
) ::
  :ok | {:error, String.t()}
```

Whether `child` may sit inside a tag that does not exist yet.

A wrap mints its wrapper on the spot, so there is no node to point `can_drop?/4` at and no place in
the tree to ask about — but the pairing still has to hold: a `<span>` will not take a `<section>`,
and an `<img>` will not take anything at all. Only the rules that are a property of the PAIR are
asked here; the ones that depend on where the wrapper lands (cycles, form nesting, an interactive
ancestor) are asked of the wrapper's destination instead, which is a real position in a real tree.

One pair is not a property of the pair alone: a link holds what its parent holds. Pass the tree
`child` sits in as `:tree`, and a link put around it holds what `child`'s parent holds, since the
wrapper takes `child`'s place; a link that is `child` is read with its own children. Without it a
link takes anything, and a link arriving is inline.

Returns `:ok` or `{:error, message}`, in the same words a refused drop uses.

# `content_element`

```elixir
@spec content_element(
  MishkaCmsCore.Runtime.Authoring.Tree.Node.t() | term(),
  keyword()
) ::
  String.t() | nil
```

The element `node`'s children land in, lowercased: a tag is itself, and a component is the element
its own markup renders its inner block in (`"tbody"` for a kit table, `"tr"` for a kit row).

`nil` for a slot, for a component whose markup cannot be read and for one that hands its content
on to another component. `opts` takes `:components`.

# `element`

```elixir
@spec element(MishkaCmsCore.Runtime.Authoring.Tree.Node.t() | map(), keyword()) ::
  String.t() | nil
```

The element `node` puts on the page, lowercased: a tag is itself, and a component is the one
element its own markup opens with (`"td"` for a kit cell), or for one of the CMS's own function
components the element it renders (`"form"` for `<.form>`). `nil` when that cannot be read.

`node` is a `Tree.Node` or a palette entry carrying `:tag`; `opts` takes `:components`.

# `holds_children?`

```elixir
@spec holds_children?(
  MishkaCmsCore.Runtime.Authoring.Tree.Node.t() | term(),
  keyword()
) :: boolean()
```

Whether this node may hold children at all, asked without a node being dragged.

The two rules `can_drop?/4` applies to the TARGET alone — what HTML lets an element contain, and a
component needing somewhere to put content — and nothing about what is arriving. The layers rail
has to answer this while it renders a row, long before anything has been picked up, and answering
it a second way is how the rail came to refuse drops the server allows: `drop_target?/2` required a
palette row, so the `<.form>` this editor wraps every loose field in — a component Phoenix declares
a slot for, and one no kit ships — was drawn as a row nothing could be dropped inside, and a field
aimed at the middle of it landed beside it instead.

A guess, not a verdict: it knows nothing about the node being moved, so `can_drop?/4` still decides.

# `misplaced`

```elixir
@spec misplaced(MishkaCmsCore.Runtime.Authoring.Tree.t(), keyword()) ::
  {MishkaCmsCore.Runtime.Authoring.Tree.Node.t(), String.t()} | nil
```

The first node of `tree`, in document order, that sits where the placement rules would have
refused it, as `{node, message}`; `nil` when every node is where they allow it.

This is `can_drop?/4` asked of a document that is already written, which is what an editor has to
answer for markup that was typed or pasted instead of dropped. It asks three of its rules, in the
words a refused drop gives:

  * anything but phrasing content inside a `<p>`, a heading, a `<span>` or another element whose
    content model is phrasing content, as the standard lists it (`@phrasing`): a `<div>`, a
    `<ul>`, a kit card or a form there is refused, a `<br>`, an `<img>`, an `<input>` or a custom
    element is not. The browser closes a `<p>` in front of a block, so the page it draws is not
    the tree that was written;
  * a form inside a form, whose inner one a browser drops;
  * an element, a component or a comment inside a text-only element.

`button` and `label` also take phrasing content in the standard, but the Chelekom kit's own tabs,
accordion, combobox, radio card, checkbox card and toggle field put a `<div>` in one, and `summary`,
`legend`, `dt` and `figcaption` take more than phrasing content. A document is held in those only
to a form or a text block (a paragraph, a heading, a `<pre>`...); a drop keeps them to phrasing
content, as it does every host.

The rest of what `can_drop?/4` asks, such as a slot to put content in, the only children of a list
or a landmark inside a landmark, stays a rule about where something may be put.

Every node is asked about on its own, so a form inside a form is reported at the inner form and
not at the `<div>` that holds it. `opts` takes `:components`, as `can_drop?/4` does.

# `parent_restricted?`

```elixir
@spec parent_restricted?(String.t() | nil) :: boolean()
```

Whether a tag may only appear under a specific parent — `<td>` outside a `<tr>` is not a cell, it is
foster-parented out of the table by the parser.

The page builder asks this before it wraps a node in its own element: an editor wrapper around a
`<td>` lands where only a `<td>` may go, so the parser moves it out and the wrapper becomes an
orphan sitting above the table, taking the node's markers with it.

# `phrasing_only?`

```elixir
@spec phrasing_only?(String.t() | nil) :: boolean()
```

Whether a tag holds phrasing content only, so no block element may be dropped into it.

# `table_homes`

```elixir
@spec table_homes(String.t() | nil) :: [String.t()] | nil
```

The elements a table part may sit directly inside — `["tr"]` for a cell — or `nil` for a tag a
browser keeps wherever it is written.

# `transparent?`

```elixir
@spec transparent?(String.t() | nil) :: boolean()
```

Whether a tag holds what its parent may hold rather than a content model of its own: an `<a>`
takes a paragraph in a `<div>` and inline content only in a `<p>`.

# `void_tags`

```elixir
@spec void_tags() :: [String.t()]
```

Tags that can never hold children.

---

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