# `StatifierBlocks.Shelf`
[🔗](https://github.com/riddler/statifier_blocks/blob/v0.44.1/lib/statifier_blocks/shelf.ex#L1)

The two placement facts `io/1` cannot carry, and the identity every other
surface asks about the shelf and the gap marker.

ADR-0002's amendment of 2026-08-31, section G12, states the two facts and
an operator ruling puts their enforcement in the compiler's
Structure stage; ADR-0004's amendment of the same date, section D3, names
the two codes. This module owns both, and owns nothing else: kind
admission, slot arity and the data-flow walk are unchanged and are still
`StatifierBlocks.Assignability`'s and `StatifierBlocks.SlotValidation`'s.

## Why this module is small on purpose

`:draft_shelf` does the placement work. Every slot in the shipped `core.*`
vocabulary accepts `[:step]` or `[:interrupt_handler]`, so a block
declaring only that kind is refused by all of them through the
intersection ADR-0003 decision 3 already performs, with no new rule, no
per-type list, and nothing in this module. What is left over is exactly
two things the intersection cannot express, and they are the only two
rules here:

| Finding | Fact |
|---|---|
| `:drafts_block_misplaced` | a shelf is a **direct child of the root block's `body` slot** and nowhere else - a constraint on *depth*, which is a property of the document rather than of either block (G12a) |
| `:duplicate_drafts_block` | a document carries **at most one** shelf - cardinality across a document, closest in shape to ADR-0001 decision 3's document-unique ids and checked the same way (G12b) |

Both refuse documents that would otherwise be admitted and neither changes
the meaning of a document that is already valid, so relaxing either later
is additive (G12c).

## Why the type name and not the module

Every predicate here matches `Block.type`, the string ADR-0002 decision 1
puts in the document, rather than resolving a palette entry and comparing
modules. The record says "a `core.drafts` block" throughout and `core.` is
the reserved namespace decision 4 provides for, so the name is what is
being talked about; a name test needs no palette, cannot disagree with one,
and leaves the compiler and the editor with no dependency on a core type
module. A host that wants its own shelf mints its own kind, its own
container and its own structure rule, which ADR-0003's amendment of this
date already records as the intended path.

## The second block, not the first

`:duplicate_drafts_block` names the second and every later shelf in
document order. The first is the one the author almost certainly means to
keep, and a finding on it would ask them to fix the block that is not the
problem - the same reasoning ADR-0001 decision 11f applies to a shadowed
root, arriving at a different rule for the same reason (D3).

# `finding`

```elixir
@type finding() ::
  {:drafts_block_misplaced, StatifierBlocks.Block.id()}
  | {:duplicate_drafts_block, StatifierBlocks.Block.id()}
```

A placement violation, in the tuple shape `StatifierBlocks.Compiler`
adapts into a `:structure` finding. The first element is the code
ADR-0004's amendment of 2026-08-31, section D3, names.

# `drafts_type`

```elixir
@spec drafts_type() :: StatifierBlocks.Block.type_name()
```

The type name a shelf is stored under.

# `marker?`

```elixir
@spec marker?(StatifierBlocks.Block.t()) :: boolean()
```

Whether this block is a gap marker.

# `marker_type?`

```elixir
@spec marker_type?(StatifierBlocks.Block.type_name()) :: boolean()
```

Whether this type name is the gap marker's.

# `placeholder_type`

```elixir
@spec placeholder_type() :: StatifierBlocks.Block.type_name()
```

The type name a gap marker is stored under.

# `root_body?`

```elixir
@spec root_body?(
  StatifierBlocks.Document.t(),
  StatifierBlocks.Block.id(),
  StatifierBlocks.Block.slot_name()
) :: boolean()
```

Whether `{parent_id, slot}` is the one position a shelf is admitted in:
the root block's `body` slot (G12a).

This is the *admission* half of the depth rule, and it has to exist as
well as the refusal half. Every container in the shipped vocabulary
declares `slot_accepts` `[:step]` or `[:interrupt_handler]`, the root
included, so the ordinary intersection refuses a `:draft_shelf`
everywhere - at the one position the record admits it as well as at every
position it does not. G12a is what says the root's `body` admits one
anyway, and `StatifierBlocks.Assignability` consults this to say so.

Reaching it through assignability rather than through a filter on the
compiler's findings is deliberate: `valid_targets/4` is the same
decision, so an author dragging a shelf onto the root's body is offered
the position for the same reason a shelf already there does not refuse.
Two answers would be two chances to disagree.

# `shelf?`

```elixir
@spec shelf?(StatifierBlocks.Block.t()) :: boolean()
```

Whether this block is the shelf.

# `shelf_type?`

```elixir
@spec shelf_type?(StatifierBlocks.Block.type_name()) :: boolean()
```

Whether this type name is the shelf's.

# `shelves`

```elixir
@spec shelves(StatifierBlocks.Document.t()) :: [StatifierBlocks.Block.t()]
```

Every shelf in `document`, in `Document.blocks/1` pre-order.

A document is well-formed with none and with exactly one; more than one
is `validate/1`'s business, not this function's.

# `validate`

```elixir
@spec validate(StatifierBlocks.Document.t()) :: :ok | {:error, [finding()]}
```

Every placement violation in `document`, in document order. `:ok` when
there are none.

The two rules are independent and both are reported: a second shelf that
is also nested inside a group carries both codes, which is ADR-0004
decision 10's "within a stage every finding is reported" - the two are
siblings rather than one being a consequence of the other.

---

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