# `MishkaCmsCore.Runtime.Authoring.Containment`
[🔗](https://github.com/mishka-group/mishka_cms_core/blob/v0.0.3/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.
  * **Phrasing-only elements** (`span`, `p`, `h1`–`h6`, `button`, `pre`, …) take text and
    other phrasing content, never a block element. This is deliberately stricter than the spec —
    `<p>` may legally hold some things this rejects — because the stricter rule is the one that
    matches what an author means to build. Ported from `containment.js`, with one correction: 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. 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.

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). `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.

# `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*
