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

What "the template" means for each of the four things the builder edits, and how to write one back.

Three of them — page, layout and component — keep their markup in a single `template` attribute
and need nothing special. **An error page does not.** It stores `error_pages`, a list of
`%{status, template, layout}` entries, so "open the error page in the builder" is really "open the
404 entry of this error page", and saving has to replace one element of that list rather than set
a field. Without this module the error-page route would appear to work and then write a `template`
attribute the resource does not have.

Keeping the difference here — rather than as branches inside the LiveView — is what lets the
editor, the canvas, the history and the save gate stay identical for all four sections.

# `allowed?`

```elixir
@spec allowed?(map() | nil, String.t() | atom()) :: boolean()
```

Whether `actor` may open the builder on `section` (`permissions/1`).

# `apply_fields`

```elixir
@spec apply_fields(map(), map() | nil) :: map()
```

Resolves what `save_fields/3` deferred against `record` — the row being written, which for a
create is `nil`.

Values that are not functions are passed through, so a caller with fields of its own can merge
them in before resolving.

# `atoms`

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

Every section as an atom — what a resource constraint or a struct's enum needs.

# `editing_status`

```elixir
@spec editing_status(String.t(), map() | nil, keyword()) ::
  MishkaCmsCore.Runtime.ErrorStatus.t() | nil
```

The status an editing SESSION is for, or `nil` for a section that has none.

`status/2` always answers a status, because an error page always opens on one. This is the other
question — "is a status part of what this document is?" — and for a page, a layout or a component
the answer is no. It is what keys an editing session, so a session opened on the 404 entry is not
handed back for the 500 one.

# `editor_url`

```elixir
@spec editor_url(
  String.t() | atom(),
  String.t() | nil,
  MishkaCmsCore.Runtime.ErrorStatus.t() | nil
) ::
  String.t() | nil
```

The visual editor's address for one document, or for making a new one when `id` is `nil` — or
`nil` when no editor is installed.

THE CORE DOES NOT SPELL THE EDITOR'S ADDRESSES. It asks `runtime:document:editor_url`, a transform
whose payload is `%{section: "page" | "layout" | "component" | "error-page", id:, status:, url: nil}`,
and the editor's own plugin answers with `url` filled in. No plugin, or one that cannot run,
leaves `url` as `nil`, and every caller then leaves its "open in the editor" button out: the
dashboards' create menu opens the form instead, and the inspector offers no Visual Editor.

`status` names the error-page entry to open, as the editor's own address does.

# `known?`

```elixir
@spec known?(String.t()) :: boolean()
```

Whether `section` is one the builder knows.

# `label`

```elixir
@spec label(String.t()) :: String.t()
```

A human name for the section, used in flash messages.

# `list_path`

```elixir
@spec list_path(String.t()) :: String.t()
```

Where the section's index page lives — the builder's back link.

# `names`

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

The section names the builder can open.

# `normalize`

```elixir
@spec normalize(String.t() | nil | atom()) :: String.t()
```

Normalizes `page`, `error_page` and `error-page` to the canonical hyphenated form.

# `offered_statuses`

```elixir
@spec offered_statuses() :: [MishkaCmsCore.Runtime.ErrorStatus.t()]
```

The statuses an error page can hold an entry for: the offline page, then the codes the Error Pages
offer (`MishkaCmsCore.Runtime.ErrorStatus.choices/0`). One the record lacks is added by opening it
in the builder.

# `permissions`

```elixir
@spec permissions(String.t() | atom()) :: [String.t()]
```

The names that open the builder on `section`, holding any one being enough: its own runtime area
or `admin:dashboard:code`.

# `read_opts`

```elixir
@spec read_opts(module(), map() | nil) :: {:ok, keyword()} | :error
```

How `actor` reads one document of `resource`, with its site loaded: `{:ok, opts}` for `Ash.get/3`,
`:error` for a viewer who is not a user.

A master reads across sites with `:master_get`, multitenancy bypassed. A site admin reads with
their site as the tenant — through `:get_for_tenant_with_globals` where the resource declares it,
because a layout or a component may belong to no site at all, and `global? true` only means the
resource may be read WITHOUT a tenant: a tenant-scoped read still filters a site-less row out. A
resource with no shared rows — a page, an error page — keeps the plain tenant read.

# `resource`

```elixir
@spec resource(String.t()) :: module()
```

The Ash resource behind a section.

# `save_fields`

```elixir
@spec save_fields(String.t(), String.t(), keyword()) :: %{
  required(String.t()) =&gt; term()
}
```

The attribute map an editor saves a section's markup with, with the
values that depend on the record left as functions of it.

For an error page the whole `error_pages` list IS the attribute, so the write is every entry with
one of them replaced — and an entry this session is not editing is only carried across correctly
if the list it is copied from is the STORED one. A record read earlier is not: the builder holds
`@record`, the snapshot its LiveView mounted on, and a tab sitting on the 404 since before the 500
was published wrote the old 500 back over the new one, reverting a publish with nothing said. So
the value is deferred rather than computed, and `apply_fields/2` resolves it against the row the
write has just read and is about to update.

Every other section sets `template` outright and never reads the record, so its value is the
markup and nothing about it can go stale.

Markup that is empty or only whitespace is saved as the document a blank create starts with,
`MishkaCmsCore.Runtime.Authoring.PageStarter.blank/1`: the layout's container around
`@inner_content` for a layout, the empty container for the rest. A template is never empty in
the database, so an author who deleted everything had a save the resource refused on every
autosave and a publish that failed. The container is what the editor reads as a document nobody
has started, so it reopens on the starter.

# `sibling_templates`

```elixir
@spec sibling_templates(String.t(), map() | nil, keyword()) :: [String.t()]
```

The templates this record owns OTHER than the one being edited.

Empty for every section but an error page, which keeps one template per status in a single record.
That matters wherever something is keyed to the RECORD rather than to the status — the element-CSS
row is, and pruning it against only the open status deleted the rules belonging to the other four.

# `status`

```elixir
@spec status(map() | nil, keyword()) :: MishkaCmsCore.Runtime.ErrorStatus.t()
```

The status the editor should open on: the requested one if the record has it or an error page can
hold it (`offered_statuses/0`, which is how a status is added), else the first.

# `statuses`

```elixir
@spec statuses(String.t(), map() | nil) :: [MishkaCmsCore.Runtime.ErrorStatus.t()]
```

The statuses an error-page record defines, codes in ascending order and then `:offline`. `[]` for
every other section — the status tab strip only renders when there is something to switch between.

# `statuses`

```elixir
@spec statuses(String.t(), map() | nil, MishkaCmsCore.Runtime.ErrorStatus.t() | nil) ::
  [
    MishkaCmsCore.Runtime.ErrorStatus.t()
  ]
```

The statuses the builder's strip shows for a record: those it defines and the one open, which may be
one being added. `[]` for every other section and for a document not yet written.

# `template`

```elixir
@spec template(String.t(), map() | nil, keyword()) :: String.t()
```

The markup the editor should open for this record.

For an error page, `opts[:status]` selects which status entry — defaulting to 404, or the first
entry the record actually has.

# `to_atom`

```elixir
@spec to_atom(String.t() | nil | atom()) :: {:ok, atom()} | :error
```

The section as the atom the log and the resources use, or `:error` for anything else.

Total on purpose: the argument is often something a URL supplied.

# `to_atom!`

```elixir
@spec to_atom!(String.t() | nil | atom()) :: atom()
```

The same, for a section already known to be one of ours. Raises rather than inventing one.

# `to_underscored`

```elixir
@spec to_underscored(String.t() | nil | atom()) :: String.t()
```

The underscored form — what an editing session stores and what the resources spell their sections
with.

ONE SECTION IS SPELLED TWO WAYS. The address says `error-page` and everything durable says
`error_page`, and that difference was converted by hand in nine places across five modules. Three
of them were bare `String.to_existing_atom/1` calls, which is not a conversion but a crash waiting
for the one section whose two spellings differ — it took the builder's mount down for error pages.

---

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