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

Components inside content: a marker an author writes into stored HTML — a post's body — drawn as
the runtime component it names. WordPress calls these shortcodes; Joomla, content plugins.

    [[component chelekom-alert title="Heads up"]]Text, <b>with</b> HTML[[/component]]
    [[component chelekom-badge size="small" /]]

A template draws such content with `rich_content/1`, imported into every runtime template — see
`MishkaCmsCore.Runtime.LiveViewHelpers.rich_content/1`:

    <.rich_content
      id={"#{@id}-body"}
      html={@body}
      site={site_name()}
      current_user={@current_user}
    />

## The marker

  * `[[component NAME ATTRS]]INNER[[/component]]` — `INNER` is HTML, and becomes the component's
    `inner_block` slot when the component declares one.
  * `[[component NAME ATTRS /]]` — no inner content.
  * `NAME` is letters, digits, `-` and `_`. It names a component of the site being drawn, or a
    global one when the site has none of that name.
  * Each of `ATTRS` is `key="value"`, `key='value'`, `key=value` or a bare `key`, which is `true`.
    A quote may arrive as an entity — `&quot;`, `&#34;`, `&#39;`, `&#x27;`, `&apos;` — and a value
    is entity-decoded. A value cannot hold both kinds of quote: the sanitizer content is stored
    through decodes every quote entity back into the quote itself.
  * Whitespace is spaces, tabs, line breaks and the non-breaking space, written or as an entity.

A marker is found only in text: never inside a tag, and never inside `<pre>` or `<code>`, so a
page can show the syntax. A marker that does not parse, an opening marker nothing closes and a
closing marker nothing opened are left in the text as they are.

## Inside a component

`INNER` may hold markers of its own, drawn inside the component — a timeline holding its sections,
a card holding its title:

    [[component chelekom-timeline]]
      [[component chelekom-timeline-section title="Started" /]]
      [[component chelekom-timeline-section title="Shipped" /]]
    [[/component]]

and, for a component that declares named slots, a slot marker for each entry it takes:

    [[component chelekom-video]][[slot source src="/intro.mp4" type="video/mp4" /]][[/component]]
    [[slot item title="Question"]]The answer, as <b>HTML</b>[[/slot]]

A slot marker is read with the same rules as a component's, belongs to the component it sits
directly inside, and is passed as that slot when the component declares it — its attributes cast
against the slot's own. A slot marker anywhere else is text. Markers are drawn eight levels deep;
the content of a deeper one is drawn as it is written.

What a component hands its content — `render_slot(@inner_block, value)`, or a slot entry's
`render_slot(entry, value)` — reaches each marker inside that content, at any depth, as
`runtime:render:before_content_component`'s `slot_arg` meta. A marker outside the component never
sees it, and a component inside that hands nothing (`render_slot(@inner_block)`) passes on the one
it was given.

A paragraph holding one marker and nothing else — whitespace and non-breaking spaces aside — is
drawn as the component alone, without the `<p>` an editor wraps a line in: a block inside a `<p>`
is HTML a browser takes apart. A marker sharing its paragraph with other text keeps it.

## What is drawn

Only a component the request's site may render: active, and the site's own or a global one — the
rule `MishkaCmsCore.Runtime.LiveViewHelpers.component/1` holds every template to. The name is
first checked, as a string, against the active components `SiteState` knows the site and the
global scope have: a name an author typed that names none of them makes no module name, no atom
and no remembered failure. A name that resolves to nothing, an inactive component, another site's,
one that draws content itself (it could draw the content that holds it, forever), one missing an
attribute it requires, and one whose list or map value cannot be read (see Lists and maps) are
drawn as nothing. The same render in a preview (`MishkaCmsCore.Runtime.StaticHelpers`) draws a
notice saying why in their place. On a page, one that passes every check and then raises while it
draws is drawn as a small box, and the content around it draws — see
`MishkaCmsCore.Runtime.ComponentBoundary`.

A component is handed what `<.component>` in a template would hand it: the attributes the marker
writes, and for one it leaves out the default the component declares, or nothing at all when it
declares none, as Phoenix leaves it. A clause matching `%{navigate: _}` is taken only when the
marker writes `navigate`, and a template reads an attribute with no default as `assigns[:label]`,
never `@label`.

Only the attributes the component declares are read from the marker, each cast to its declared
type: `string` and `any` as written, `boolean` from `true`/`false` or a bare key, `integer` and
`float` from a number, `list` and `map` from JSON (see Lists and maps), and anything that declares
`values` only as one of them — which is the only way an `atom` is taken. `id`, `site`, `component_name`, `current_user`, `rest` and any `phx-` name are never an
author's. A stateful component, and one declaring `id`, gets the id `"<id>-<n>"`, `n` counting
the content's markers from 0 — `"<id>-<n>-<m>"` for the `m`th inside it — so it stays the same
component across renders.

A component declaring a `global` attribute (`attr :rest, :global, include: ~w(disabled circle)`)
takes, through it, the HTML attributes that attribute names and a few every element has: each
name in its `include` list, `aria-*`, `data-*`, and `accesskey`, `autocapitalize`,
`autocorrect`, `autofocus`, `dir`, `draggable`, `enterkeyhint`, `hidden`, `inert`, `inputmode`,
`lang`, `role`, `spellcheck`, `tabindex`, `title` and `translate`. A bare key or `"true"` is
`true`, `"false"` leaves it out, and anything else is the text written. Never an event handler
(`on…`), `style`, a `phx-` or `data-phx-` name, `data-method`, `data-to`, `data-csrf`, `method`,
`csrf_token` or a name the component declares, and a link or source (`href`, `src`, `action`,
`formaction`, `poster`, `cite`, `ping`) only when it is a path or an `http`, `https`, `mailto` or
`tel` address:

    [[component my-video controls muted]][[slot source src="/intro.mp4" /]][[/component]]
    [[component my-button circle disabled aria-label="Scan"]][[/component]]

A slot the component declares and the content does not fill is passed empty, and one it requires
draws the component as nothing — as a required attribute does. A slot's attribute the marker
leaves out is passed as its declared default, and an entry with nothing inside it draws nothing
where the component renders its content.

The HTML around the markers is drawn as it is given: pass only HTML that was sanitized.

## Lists and maps

An attribute declared `list` takes a JSON array, and one declared `map` a JSON object, written in
single quotes around JSON's own double ones:

    [[component chelekom-headless-tree nodes='[{"value":"src","label":"src","children":[{"value":"app","label":"app.ex"}]}]' /]]
    [[component chelekom-headless-sparkline values='[3,5,2,8,6]' /]]
    [[component chelekom-headless-segmented-control options='["Day","Week","Month"]' value="Week" /]]
    [[component chelekom-headless-countdown seconds="93784" show_labels labels='{"days":"d","hours":"h"}' /]]

An `any` attribute that declares no `values` takes an array or an object the same way, and every
other value as it is written: `option='{"series":[]}'` is a map, `option="{draft"` the text.

The value reaches the component as data: strings, numbers, `true`, `false` and `null` as Elixir's
own, an array as a list, an object as a map. Nothing in it is run, and no string in it becomes a
module or an atom. An object's keys are atoms when it is a record — an object inside an array,
such as a tree's node read as `node.label`, and the object of an attribute whose declared default
is a map with atom keys. Any other object keeps its keys as written: a lookup such as a
countdown's `labels`, read by the strings its `units` hold. A key becomes an atom only when the VM
already has that atom — as it has every key the component's code reads — and a key starting with
`__` is always a string.

A value is at most 32 KB as written, and 8 levels deep, each array and object a level: a tree's
node is two. A `'` inside the JSON is written `\u0027`, and `&`, `<` and `>`, as in any value, as
entities. A `list` or `map` value that is not JSON of its kind, that is larger or deeper, or that
is a bare key draws the component as nothing, and a preview says which attribute and why. An
`any` value is then handed as it is written.

## Cost

Content holding no `[[component` is drawn as it is, and nothing is parsed. A marker asks only what
the runtime keeps in memory: `SiteState` for the site and its component names, then the loaded
code table for the compiled module. The first render of a component nothing has built yet builds
it, once, as `component/1` does.

## Styles

A site's stylesheet is built from the templates it reaches
(`MishkaCmsCore.Runtime.Compilers.CssClosure`), and the components content names are in no
template. Whoever stores content answers `runtime:compile:content_components` with the names
its content uses — see `MishkaCmsCore.Runtime.Compilers.TailwindCompiler.content_components/1` —
and a template that draws content reaches exactly those. `names/1` reads them out of content, to
store beside it when it is written.

The icons content names are in no template either: a marker's `icon="hero-inbox"` reaches the
component as an expression, and a `<span class="hero-inbox">` in the HTML is never scanned. The
same event answers them as `icons` — see
`MishkaCmsCore.Runtime.Compilers.TailwindCompiler.content_icons/1` — and `icons/1` reads them out
of content.

Nor are the classes content writes: a `<div class="bg-[#282828]">` in the HTML, a marker's
`class`, its `*_class` attributes, and the attributes its component takes a class string in. The
same event answers them as `classes` — see
`MishkaCmsCore.Runtime.Compilers.TailwindCompiler.content_classes/1` — and `site_classes/2` reads
them out of content.

## The editor

The admin's content editor holds content as `segments/1` splits it: the HTML it edits, and each
component standing alone as a block it shows drawn. The block writes its marker back as a
paragraph of its own, so content saved from the editor stores what it was opened with. Its
palette is `palette/2`.

## Events

  * `runtime:render:before_content_component` — before each embedded component is drawn, with
    `%{component_name: name, site: site, attrs: attrs, inner: inner_html | nil}` and
    `%{site_id: site_id, index: n, marker_attrs: written, slot_arg: arg, slot_args: args}` as
    meta. `attrs` is atom-keyed and already cast; `marker_attrs` are the attributes as the marker
    writes them, string-keyed and uncast. `slot_arg` is the value the nearest component around the
    marker hands its content (`nil` when none does), and `slot_args` every such value, nearest
    first. A plugin answers the map with other `attrs` or `inner` to change what is drawn — an
    attribute taken out of `attrs` is not handed at all, for a component's own clause to fill, as
    a form field's `name` from its `field` — or `suppress: true` to draw nothing.
  * `runtime:component:crashed` — a marker's component raised while it was drawn, and a box was
    drawn in its place; see `MishkaCmsCore.Runtime.ComponentBoundary`.
  * `runtime:content:editor_palette` — before the content editor's palette is shown, with
    `%{site_id: site_id, components: entries}` (`embeddable/2`'s entries) and
    `%{current_user: user}` as meta. A plugin answers the map with other `components` to narrow or
    extend what an author may place; the editor places only what the palette lists.

# `attr_entry`

```elixir
@type attr_entry() :: %{
  name: String.t(),
  type: String.t(),
  values: [term()],
  default: term(),
  doc: String.t() | nil,
  required: boolean(),
  group: String.t(),
  media: boolean()
}
```

An attribute a marker can set, as `embeddable/2` lists it: `group` is the heading the page
builder's Inspector files it under, untranslated, and `media` whether it holds a picture or a
video.

# `chunk`

```elixir
@type chunk() :: {:html, String.t()} | {:component, marker()}
```

A piece of content: HTML drawn as it is, or a marker.

# `entry`

```elixir
@type entry() :: %{
  name: String.t(),
  description: String.t() | nil,
  type: String.t() | nil,
  stateful: boolean(),
  global: boolean(),
  inner_block: boolean(),
  category: String.t(),
  examples: [String.t()],
  furnishing: MishkaCmsCore.Builder.Furnishing.t() | nil,
  switches: switches(),
  attrs: [attr_entry()],
  slots: [
    %{
      name: String.t(),
      doc: String.t() | nil,
      required: boolean(),
      inner: boolean(),
      attrs: [attr_entry()]
    }
  ]
}
```

A component an author may embed, as `embeddable/2` lists it.

# `marker`

```elixir
@type marker() :: %{
  name: String.t(),
  attrs: %{required(String.t()) =&gt; String.t() | true},
  inner: String.t() | nil,
  index: non_neg_integer()
}
```

One marker, as `parse/1` found it.

# `segment`

```elixir
@type segment() ::
  {:html, String.t()}
  | {:component,
     %{
       name: String.t(),
       attrs: %{required(String.t()) =&gt; String.t() | true},
       inner: String.t() | nil,
       marker: String.t(),
       source: String.t()
     }}
```

A piece of content as the editor holds it: HTML, or a component standing alone. `marker` is the
marker as written, `source` the bytes it was cut from, the paragraph around it included.

# `switches`

```elixir
@type switches() :: %{
  helpers: [[%{required(String.t()) =&gt; [String.t()]} | :unknown]],
  through: [String.t()]
}
```

Which clause of each of a component's helpers draws it: one list of clauses per helper, in order,
each the attributes and the values it matches — `%{}` matching any — or `:unknown` when it matches
on something no attribute holds; and the attributes whose every use in the template is through
those helpers.

# `tree`

```elixir
@type tree() :: %{
  name: String.t(),
  attrs: %{required(String.t()) =&gt; String.t() | true},
  parts: [
    text: String.t(),
    component: tree(),
    slot: %{name: String.t(), attrs: map(), parts: list()}
  ]
}
```

One component marker read apart, as `read/1` gives it and `write/1` takes it: its name, its
attributes, and what it holds, in order — text, the components inside it, and its slot entries.

# `cast`

```elixir
@spec cast([map()], %{required(String.t()) =&gt; String.t() | true}) :: %{
  required(atom()) =&gt; term()
}
```

The attributes of `given` a component declaring `declared` takes, cast to their declared types.

`declared` is the component's attribute declarations, as `__component_information__/1` or the
row holds them; `given` is a marker's `attrs`. What is not declared, cannot be cast, or is one of
the names an author never sets is left out. The HTML attributes a `global` attribute takes are
gathered under its name, as the module documentation says.

    cast([%{name: "count", type: "integer"}, %{name: "open", type: "boolean"}],
         %{"count" => "3", "open" => true, "phx-click" => "boom"})
    #=> %{count: 3, open: true}

    cast([%{name: "rest", type: "global", opts: %{"include" => ["circle"]}}],
         %{"circle" => true, "aria-label" => "Scan", "onclick" => "boom"})
    #=> %{rest: %{:circle => true, "aria-label" => "Scan"}}

    cast([%{name: "nodes", type: "list"}, %{name: "labels", type: "map"}],
         %{"nodes" => ~s([{"label":"src"}]), "labels" => ~s({"days":"d"})})
    #=> %{nodes: [%{label: "src"}], labels: %{"days" => "d"}}

# `category`

```elixir
@spec category(String.t(), String.t() | nil) :: String.t()
```

The palette heading a component is listed under — `"text"`, `"media"`, `"layout"`, `"buttons"`,
`"data"`, `"disclosure"`, `"feedback"`, `"navigation"`, `"overlays"`, `"forms"`, a kit's own
type, or `"other"` — from its kit `type` and the words of its `name`.

    category("chelekom-h2", "general")   #=> "text"
    category("chelekom-video", "media")  #=> "media"
    category("acme-hero", "marketing")   #=> "marketing"

# `category_label`

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

A palette heading's name, in the reader's language.

# `class?`

```elixir
@spec class?(term()) :: boolean()
```

Whether `token` could be a class: at most 512 bytes — an arbitrary value such as
`mask-linear-[to_bottom,transparent_0,black_min(40px,var(--edge)),…]` runs past a hundred —
printable ASCII holding a letter, its brackets and parentheses balanced, and none of `"`, `\`,
`{`, `}`, `<`, `;` or a backtick in it.

    class?("bg-[#282828]")
    #=> true
    class?("{brace}")
    #=> false

# `classes`

```elixir
@spec classes(String.t() | nil, %{required(String.t()) =&gt; [String.t()]}, %{
  required(String.t()) =&gt; map()
}) :: [String.t()]
```

The class tokens `html` names, each once — what a resource stores beside its content when it is
written, for the site's stylesheet. The HTML's own class lists come first, then the markers':

  * every `class="…"` of an HTML tag, entities decoded;
  * a component's or a slot's `class` attribute, and every attribute named `*_class`;
  * a component's attribute `open` names for it — `%{"chelekom-badge" => ["size", "variant"]}`,
    what `open_attrs/2` reads off the components a site reaches: an attribute that takes one of
    its examples or a class string of the author's own;
  * any other attribute of a component or a slot whose value is nothing but utilities — every
    token holding a `-`, `:` or `[`, and none an icon: `height="h-16"`, `relative="md:relative"`.
    Of a component `declared` holds the declaration of — `%{"my-badge" => %{attrs: [...],
    slots: [...]}}`, what `site_classes/2` reads off the components a site reaches — and of its
    slots, only an attribute that declaration takes the value in (`cast/2`): a value outside an
    attribute's `values` never reaches the component.

A token is kept when it could be a class (`class?/1`). At most 1000 are kept.

    classes(~s(<p class="text-sm bg-[#282828]">x</p>[[component chelekom-badge size="py-2 px-4" class="ml-1" /]]),
      %{"chelekom-badge" => ["size"]})
    #=> ["text-sm", "bg-[#282828]", "py-2", "px-4", "ml-1"]

# `draw`

```elixir
@spec draw(map(), :live | :static) :: Phoenix.LiveView.Rendered.t()
```

Draws `assigns.html` with its markers drawn as components — the body of `rich_content/1`.

`mode` is `:live` where LiveView runs the result, and `:static` where it is rendered to a string:
a stateful component is then drawn cold, and a marker that draws nothing says why.

# `draws_content?`

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

Whether a component template draws content itself — calls `rich_content`. Such a component is
never drawn inside content, which could hold a marker for it.

# `embeddable`

```elixir
@spec embeddable(map() | nil, String.t() | nil) :: {:ok, [entry()]} | {:error, term()}
```

The components an author may embed on the site `site_id`, with the attributes a marker can set —
what the editor's picker offers. `nil` is the global components alone.

Active components of the site and global ones, every one of them, read as `actor` through
`:for_tenant_with_globals`; a site's own component hides the global one of the same name, as it
does when drawn. Components that draw content themselves are left out, and so are those requiring
an attribute the editor has no field for — a list or a map, which a marker is written with by
hand (see Lists and maps), or a struct — and every such attribute, and every one `cast/2` would
drop.

# `entry`

```elixir
@spec entry(map()) :: entry()
```

The palette entry for a component `row` — a stored component, or anything with its fields — as
`embeddable/2` lists it.

# `icons`

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

The icon classes `html` names, each once, in the order they first appear — a marker's attribute
(`[[slot item icon="hero-inbox"]]`) and the HTML's own classes (`<span class="hero-inbox">`) alike
— what a resource stores beside its content when it is written, for the site's stylesheet.

A class is found by the prefixes `MishkaCmsCore.Runtime.PageBuilder.Icons` registers
(`Icons.classes_in/1`), and kept only when a registered set ships it (`Icons.known?/1`): a
`hero-section` in the text, or a prefix nothing registers, is left out.

    icons(~s([[slot item icon="hero-cloud" /]]<span class="hero-inbox size-4"></span>hero-nope))
    #=> ["hero-cloud", "hero-inbox"]

# `inert`

```elixir
@spec inert(entry(), String.t(), map()) :: %{required(String.t()) =&gt; String.t()} | nil
```

Whether the attribute `name` of the palette `entry` draws nothing with the other attributes at
`values` — the marker's, the rest at their defaults: the values of the other attributes that pick
a clause of every helper it goes to that matches all of its own values alike, or `nil` when it
draws something, or when that cannot be told.

    inert(blockquote, "color", %{"variant" => "base"})
    #=> %{"variant" => "base"}

# `marker`

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

The marker for component `name` with `attrs` and `inner` content — what the editor's Insert writes.

A blank value is left out, so the component's own default applies; a blank `inner` writes the
self-closing form. Attributes are written in name order, `&`, `<` and `>` in a value as entities.
A value holding both kinds of quote has its double quotes written as single ones, since stored
content cannot carry an escaped quote. `nil` when `name`, or a key, could not be read back as
one, or is a key no author sets.

    marker("chelekom-alert", %{"title" => "Heads up", "size" => ""}, "Read <b>this</b>")
    #=> ~s([[component chelekom-alert title="Heads up"]]Read <b>this</b>[[/component]])

# `names`

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

The component names the markers in `html` name, each once, in the order they first appear — those
inside other markers too — what a resource stores beside its content when it is written, for the
site's stylesheet.

    names(~s([[component chelekom-alert /]]<p>x</p>[[component chelekom-card]][[component chelekom-alert /]][[/component]]))
    #=> ["chelekom-alert", "chelekom-card"]

# `open_attrs`

```elixir
@spec open_attrs([String.t()], String.t() | nil) :: %{
  required(String.t()) =&gt; [String.t()]
}
```

The attributes each component `names` names takes a class string in — one declaring `examples`
and no `values`, a `string` or an `any` — as `classes/2` takes them. Read off the active
components of the site `site_id` and the global ones, a site's own hiding the global one of the
same name, as the system: the stylesheet is the same whoever wrote the content.

    open_attrs(["chelekom-badge"], site_id)
    #=> %{"chelekom-badge" => ["size", "variant", "rounded"]}

# `palette`

```elixir
@spec palette(map() | nil, String.t() | nil) :: {:ok, [entry()]} | {:error, term()}
```

The components the content editor's palette offers on the site `site_id`, read as `actor` —
`embeddable/2`, as a plugin answering `runtime:content:editor_palette` leaves it.

# `parse`

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

Splits `html` into the HTML drawn as it is and the markers in it, in order.

    parse(~s(<p>Hi</p>[[component chelekom-badge size="small" /]]))
    #=> [
    #=>   {:html, "<p>Hi</p>"},
    #=>   {:component, %{name: "chelekom-badge", attrs: %{"size" => "small"}, inner: nil, index: 0}}
    #=> ]

Content with no `[[component` in it is one `{:html, html}`, found with a single binary match. Only
the outermost markers are pieces: a marker inside another is part of that one's `inner`.

# `read`

```elixir
@spec read(String.t()) :: {:ok, tree()} | :error
```

One component marker read apart into a `t:tree/0` — what the content editor's settings edit.
`:error` when `marker` is not exactly one component marker.

    read(~s([[component chelekom-card]][[component chelekom-card-title title="Hi" /]]Text[[/component]]))
    #=> {:ok, %{name: "chelekom-card", attrs: %{}, parts: [
    #=>   {:component, %{name: "chelekom-card-title", attrs: %{"title" => "Hi"}, parts: []}},
    #=>   {:text, "Text"}
    #=> ]}}

# `segments`

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

Splits `html` into the HTML the content editor edits and the components standing alone in it,
which it holds as blocks.

A marker stands alone when its paragraph holds nothing else — the marker `parse/1` draws without
the `<p>` — or when it sits inside no element at all. A marker sharing its paragraph with text
stays in the HTML around it. Each component's `source` is what it was cut from, so the pieces'
HTML and the components' `source`, joined in order, are `html` again.

    segments(~s(<p>Hi</p><p>[[component chelekom-badge size="small" /]]</p>))
    #=> [
    #=>   {:html, "<p>Hi</p>"},
    #=>   {:component, %{name: "chelekom-badge", attrs: %{"size" => "small"}, inner: nil,
    #=>     marker: ~s([[component chelekom-badge size="small" /]]),
    #=>     source: ~s(<p>[[component chelekom-badge size="small" /]]</p>)}}
    #=> ]

# `site_classes`

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

The class tokens `html` names on the site `site_id` — `classes/2`, with the attributes the
components it names take a class string in (`open_attrs/2`).

# `slot_marker`

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

The slot marker for the slot `name` with `attrs` and `inner` content, written as `marker/3` writes
a component's.

    slot_marker("source", %{"src" => "/a.mp4", "type" => "video/mp4"}, nil)
    #=> ~s([[slot source src="/a.mp4" type="video/mp4" /]])

# `sort_categories`

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

`categories`' names in the order the palette shows them: text first, the ones above in their
order, a kit's own after them by name, and Other last.

# `unavailable_class`

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

The classes a preview's notice is drawn with — a marker that draws nothing, or a component that
could not be drawn — for whatever builds the stylesheet a preview is drawn with.

# `write`

```elixir
@spec write(tree()) :: String.t() | nil
```

The marker for `tree`, with every component and slot inside it — `read/1` the other way. `nil`
when a name or a key in it could not be read back (`marker/3`).

---

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