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

A database page's starting state: the assigns it declares in `Page.state`, put on the socket by
the LiveView that serves the page.

A page module is not a LiveView — `MishkaCmsWeb.RuntimeLive` owns `mount/3` and
`handle_params/3` — so until a page could declare these, its template could not read `@count`
before an event had assigned it. The ways around it were a default in every expression
(`assigns[:count] || 0`, wrong for `false`) or a helper handed `assigns`, and LiveView cannot track
what a function does with `assigns`: every update re-sent each such expression.

## What the compiler emits

`MishkaCmsCore.Runtime.Compilers.LiveViewCompiler.build_page_state/1` turns

    state: [%{name: "count", code: "0"}, %{name: "roles", code: "roles()"}]

into

    def __page_state__, do: %{count: 0, roles: roles()}
    def __page_state_keys__, do: [:count, :roles]

`__page_state__/0` runs once per visitor, when the page is mounted — the expressions may call the
page's helpers, and `DateTime.utc_now/0` is fresh for each visitor. `__page_state_keys__/0` is a
literal, so the runtime can tell which keys a page owns without running its code.

## Where the runtime calls this

  * `assign_initial/2` — `mount/3`, once the page module is known.
  * `switch/3` — `handle_params/3`. Only when the address now names ANOTHER page in the same
    process: that page's state is assigned and the keys only the previous page declared become
    `nil`, so nothing travels from one page to the next. The same page — a query string that
    changed, or the call right after `mount/3` — keeps the visitor's state untouched.
  * `fill_missing/2` — when the page is edited while it is open: the keys the new version
    declares and the socket does not have yet are added, so the template's new `@key` does not
    raise; the visitor's own values stay.

## Names

`name` must match `^[a-z_][a-zA-Z0-9_]*$`, must not start with `__`, must be unique in the list
and must not be one of `reserved/0` — the assigns the runtime and LiveView already own. The atoms
are created when the page compiles, from names that passed this validation, never at request time.

# `assign_initial`

```elixir
@spec assign_initial(Phoenix.LiveView.Socket.t(), module()) ::
  Phoenix.LiveView.Socket.t()
```

Puts the page's starting state on the socket. A page that declares none is untouched.

# `fill_missing`

```elixir
@spec fill_missing(Phoenix.LiveView.Socket.t(), module()) ::
  Phoenix.LiveView.Socket.t()
```

Adds the keys `module` declares that the socket does not have yet, and leaves every other value
as the visitor left it.

# `keys`

```elixir
@spec keys(module() | nil) :: [atom()]
```

The keys `module` declares, without running its code.

# `of`

```elixir
@spec of(module() | nil) :: map()
```

The page's starting state, evaluated now — `%{}` for a module compiled before state existed or
for a page that declares none.

# `reserved`

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

The assign names a page's state may not take.

# `switch`

```elixir
@spec switch(Phoenix.LiveView.Socket.t(), module() | nil, module()) ::
  Phoenix.LiveView.Socket.t()
```

Moves the socket from `previous`'s state to `module`'s, when they are different pages.

The keys only `previous` declared become `nil`; `module`'s are assigned from scratch. The same
module answers the socket untouched.

# `valid_name?`

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

Whether `name` may name a state entry, before uniqueness.

---

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