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

ETS cache of who a signed-in user is to the site gates: their site, their address and the names of
the permissions they hold, and whether they are a dashboard admin and an API admin
(`MishkaCmsCore.Accounts.Helper.dashboard_admin?/1` and `api_admin?/1`). A request that has not
loaded its user — a file, a live navigation — is answered from here by the user's id, with no
query, and every `Helper` admin predicate and `MishkaCmsCore.Accounts.AuthGate.signs_out?/2`
answer a cached viewer exactly as they answer the user.

An entry is written by the page pipeline, which loads the user fresh on every request, and is
dropped whenever that user's access changes: `MishkaCmsCore.Accounts.LiveSockets.disconnect/1` calls
`forget/1`, which clears it on every node. A user with no entry is answered as someone who is not
an admin until their next page load writes one, so a gap closes rather than opens.

Each write and drop is announced on `runtime:state:viewer:changed` as `%{op: :save | :delete, key:
user_id}`, once per node.

# `viewer`

```elixir
@type viewer() :: %{
  id: String.t(),
  site_id: String.t() | nil,
  email: String.t(),
  permissions: [%{name: String.t()}],
  dashboard_admin?: boolean(),
  api_admin?: boolean()
}
```

A user as the site gates see them.

# `child_spec`

Returns a specification to start this module under a supervisor.

See `Supervisor`.

# `forget`

```elixir
@spec forget(String.t()) :: :ok
```

Drops a user's entry on every node.

# `get`

```elixir
@spec get(String.t() | nil) :: viewer() | nil
```

The cached viewer for a user id, or `nil`.

# `remember`

```elixir
@spec remember(map() | nil) :: :ok
```

Writes `user` as the gates should see them, when it differs from what is cached.

# `start_link`

---

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