# `MishkaCmsCore.Runtime.PageBuilder.Placement`
[🔗](https://github.com/mishka-group/mishka_cms_core/blob/v0.0.1/lib/mishka_cms_core/runtime/page_builder/placement.ex#L5)

Where a thing lands — the answer shared by a click, a drag, and a template that arrives on its own.

This sits beside `MishkaCmsCore.Runtime.PageBuilder.Containment`, which answers a different
question: containment says whether a child MAY go in a parent, and placement decides which parent
and which index a gesture actually meant. Every route into the document goes through one function
here, which is what stops three doors disagreeing about the same drop.

## The two rules

A **leaf** — a kit component, a heading, an image — goes inside the element that was clicked when
it will take one, and immediately after it when it will not. The fallback is the point rather than
a consolation: clicking a `<.component>` with no slot, an `<img>` or an `<input>` is not a mistake,
it is someone pointing at the place they want the new thing to appear. Webflow, Elementor and
Bricks all land there too.

A **band** — a `<section>`, a `<main>`, anything dropped from the Blocks library — is a horizontal
slice of the page and belongs beside the other slices. A click pointing into one climbs out of the
whole branch. Without that, a call-to-action clicked while the page header was selected became a
third item in the header's flex row and rendered at a quarter width. Bricks documents Section as
root-level; Elementor's containers refuse `section` outright.

## What counts as a place to put a band

`@band_hosts` is the two obvious tags plus the two cases that matter on a real page: anything that
already holds a band, and anything still empty. A page wrapped in `<div id="app">` keeps its bands
in that div, and a rule that knew only the tag names would lift every new section out of the
wrapper. An empty box is a hole to put things in — the one container that cannot squeeze a band
into a narrow third column, because there is nothing beside it. The canvas already draws exactly
these hollow.

## Hints are not decisions

`hints/3` answers every node at once so the canvas can paint the whole tree at `dragstart`, and it
asks `Containment` directly. It deliberately does NOT go through `EditorSession`'s own gate, which
fires the `builder:containment` plugin event — that runs on the drop, once, where a plugin author's
code belongs; running it here would put it on the drag's critical path several hundred times.

A hint can also go stale: a collaborator can move the tree under a drag in flight. The drop decides.

# `after_node`

```elixir
@spec after_node(
  MishkaCmsCore.Runtime.PageBuilder.Tree.t(),
  MishkaCmsCore.Runtime.PageBuilder.Tree.Node.t()
) :: {String.t() | :root, non_neg_integer()}
```

The `{parent_id, index}` immediately after `node` among its own siblings.

# `head`

```elixir
@spec head(String.t() | MishkaCmsCore.Runtime.PageBuilder.Tree.t() | nil) ::
  MishkaCmsCore.Runtime.PageBuilder.Tree.Node.t() | nil
```

The first root of `template`, which is what the containment rules get asked about and what decides
whether this is a band.

`nil` when the template will not parse — the insert is about to fail on the same parse, so the
failure stays in one place rather than being guessed at twice.

# `hints`

```elixir
@spec hints(MishkaCmsCore.Runtime.PageBuilder.Tree.t(), term(), keyword()) :: %{
  required(String.t()) =&gt; boolean()
}
```

Which nodes will accept `dragged`, keyed by node id — every node in the tree, so the canvas can
colour all of them from one answer.

`opts` takes `:components` (the palette, for resolving a component's own containment),
`:dragged_tree` (the tree `dragged` belongs to, so a Block's own form is visible to the
form-nesting rule) and `:locked` (a `MapSet` of ids the author has pinned). A locked node refuses
even when the rules would allow it, and dragging something locked refuses everywhere at once:
nowhere will take it, and saying so up front is what stops the canvas painting a line it would
then have to withdraw.

# `index_after`

```elixir
@spec index_after(
  MishkaCmsCore.Runtime.PageBuilder.Tree.t(),
  MishkaCmsCore.Runtime.PageBuilder.Tree.Node.t()
) :: non_neg_integer() | nil
```

The index immediately after `target` among its siblings, or `nil` when it has no place there.

Separate from `after_node/2` because the caller that retries a refused drop needs the index on its
own, to merge into a payload that already names the parent.

# `note`

```elixir
@spec note(
  MishkaCmsCore.Runtime.PageBuilder.Tree.t(),
  String.t() | nil,
  String.t() | :root
) ::
  String.t() | nil
```

A sentence for a landing place the selection ring does not explain on its own, or `nil` when it
needs no words.

Landing beside the element that was clicked is visible: the insert makes the new node the selection
and the canvas scrolls to it. Landing somewhere else entirely, because the click was on a box buried
inside a band, is not — so that one says where it went, in the words the drag path already uses.

# `relocation`

```elixir
@spec relocation(
  MishkaCmsCore.Runtime.PageBuilder.Tree.t(),
  MishkaCmsCore.Runtime.PageBuilder.Tree.Node.t() | nil,
  String.t() | nil,
  String.t() | :root,
  keyword()
) :: String.t() | nil
```

The sentence for where a click placed `head`, or `nil` when the landing explains itself: a band's
`note/3`, or for a leaf that landed further out than beside the clicked node, why the clicked
node's parent refused it.

`opts` are `target/4`'s.

# `spoken`

```elixir
@spec spoken(MishkaCmsCore.Runtime.PageBuilder.Tree.Node.t(), keyword()) :: String.t()
```

A node in a sentence about where something was placed: a working form by its people name, a form
that does not work yet as "the form", anything else as `:other` (default "the element around
it"). `:components` is the palette.

# `subtree`

```elixir
@spec subtree(String.t()) :: MishkaCmsCore.Runtime.PageBuilder.Tree.t() | nil
```

The template's own tree.

A caller that only needs the head can hand this straight to `head/1`, and one asking `target/4`
where a Block may land needs the whole thing: a block opens with a band tag by design, so its
`<.form>` is a CHILD of the head and the form-nesting rule cannot see it from the head alone.

`nil` when the template will not parse.

# `target`

```elixir
@spec target(
  MishkaCmsCore.Runtime.PageBuilder.Tree.t(),
  MishkaCmsCore.Runtime.PageBuilder.Tree.Node.t() | nil,
  String.t() | nil,
  keyword()
) :: {String.t() | :root, non_neg_integer()}
```

The `{parent_id, index}` a click on `node_id` means for `head`, the first root of the template
being placed.

`nil` for `node_id` — or an id that is no longer in the tree — means the end of the document, which
is what a click on empty canvas is. `opts` takes `:components`, `:dragged_tree` (the template's own
tree, so a Block's own form is visible to the form-nesting rule) and `:band?`, the last for a
Block, which is a band whatever its markup happens to open with.

---

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