# `PhoenixKit.HeaderWidget`
[🔗](https://github.com/BeamLabEU/phoenix_kit/blob/v3.5.0/lib/phoenix_kit/header_widget.ex#L1)

A sticky LiveView a module puts into the admin header, before the notifications bell
(`c:PhoenixKit.Module.header_widgets/0`).

A module returns tuples; core normalises them into this struct
(`PhoenixKit.ModuleRegistry.all_header_widgets/0`) and the admin layout renders each one
with `Phoenix.Component.live_render/3` (`sticky: true`, `id: id`).

## Fields

  * `:live_view` - the LiveView module to render.
  * `:id` - the DOM id of the nested LiveView (not one the admin layout uses itself, see
    `reserved_ids/0`). Stable: a sticky LiveView keeps its state
    (and its PubSub subscriptions) across admin navigation only while the id is the same.
    Two widgets with one id cannot both be rendered; the first one registered wins.
  * `:session_fun` - `(layout_assigns -> session)`, called on every render of the
    layout with the layout's assigns. They include `:socket` (the parent LiveView's
    socket), `:phoenix_kit_current_scope` and `:phoenix_kit_current_user` (the user of
    the scope, filled in when the caller passed only the scope), `:current_locale`,
    `:current_locale_base` and `:impersonating?` (see `impersonating?/1`). It returns the
    session of the nested LiveView: a map with **string** keys and values that survive
    being signed into the page, so ids and strings, not structs. Or `nil`: no widget for
    this user, nothing is rendered (and no dock) and nothing is logged. Check what the
    user may see here, where the scope is at hand, rather than after the widget's process
    has started.
  * `:dock` - `true` when the widget uses the dock column (`#pk-dock`, see
    `PhoenixKitWeb.Components.AdminDock`). The column exists only while a rendered widget
    declares it.
  * `:module` - the module that declared the widget. Filled in by the registry.
  * `:session` - the result of `:session_fun`, filled in by
    `PhoenixKit.ModuleRegistry.header_widgets_for/1`.

## Authority

The session is signed, not encrypted: whoever has the page can read it. Ids and strings the
widget needs, never a secret or a token.

The session is all the widget gets: a nested sticky LiveView is mounted without the
`live_session`'s `on_mount` hooks and without `current_scope`, and for every signed-in user
of an admin-layout page. The widget has to check the permission itself from the session
(see `c:PhoenixKit.Module.header_widgets/0`).

The keys `user_token`, `pk_session_accounts`, `pk_own_tokens`, `pk_impersonators`,
`pk_organization_token`, `live_socket_id` and `phoenix_kit_session_uuid` are reserved
(`reserved_session_keys/0`): they belong to the cookie session, which LiveView
merges under the signed session of the nested LiveView, so a value here would hide the real
one from the session and impersonation checks. They are dropped from the session (one
warning per module and widget, without the values).

`session_fun` receives the layout's assigns, **not** the cookie session and not the session
token. A widget that needs the session reference or an impersonation check does it itself
in its connected `mount/3`: LiveView merges the cookie session from `connect_info` into the
session of every LiveView, so `PhoenixKit.Users.Auth.SessionRef.from_session/1` and
`PhoenixKitWeb.Users.MultiSession.impersonated?/1` can be called there on the `session`
argument (both arrive with the agent-execute PR; this branch does not depend on them).
`:impersonating?` in the assigns is a hint for rendering: it is read from
`scope.multi_session_accounts` and is always `false` while multi-session is off, so it is
not a guarantee. Keep secrets out of the session map: a nested LiveView's session is
signed, not encrypted.

# `entry`

```elixir
@type entry() ::
  {live_view :: module(), id :: String.t(), session_fun()}
  | {live_view :: module(), id :: String.t(), session_fun(),
     opts :: [{:dock, boolean()}]}
```

What `c:PhoenixKit.Module.header_widgets/0` returns, one element per widget.

# `session_fun`

```elixir
@type session_fun() :: (map() -&gt; map() | nil)
```

# `t`

```elixir
@type t() :: %PhoenixKit.HeaderWidget{
  dock: boolean(),
  id: String.t(),
  live_view: module(),
  module: module() | nil,
  session: map() | nil,
  session_fun: session_fun()
}
```

# `impersonating?`

```elixir
@spec impersonating?(PhoenixKit.Users.Auth.Scope.t() | nil) :: boolean()
```

True while acting through a borrowed account or an organization's slot.

The layout passes this as `:impersonating?` to the session functions. The
account list distinguishes accounts added with their own credentials (`own?`) from
borrowed ones. Older lists without that field treat a non-root account as borrowed.
Token-loaded organization provenance and impersonator attribution also count when the
switcher is off. A widget that must not run for
an operator borrowing a user's session returns `nil` from its session function when it is
`true`.

# `new`

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

Normalise one entry of `c:PhoenixKit.Module.header_widgets/0`.

`{live_view, id, session_fun}` or `{live_view, id, session_fun, opts}` with
`opts = [dock: boolean]`. Returns `{:error, reason}` (a string) for anything else.

# `reserved_ids`

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

The DOM ids of the admin layout that a widget may not use.

# `reserved_session_keys`

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

The session keys a widget's session function cannot set: they are dropped.

# `resolve_session`

```elixir
@spec resolve_session(t(), map()) ::
  {:ok, t(), dropped_keys :: [String.t()]} | :skip | {:error, String.t()}
```

Run the widget's session function against the layout's assigns.

Returns `{:ok, widget}` with `:session` filled in, `:skip` when the function returned
`nil` (no widget for this user), or `{:error, reason}` when the function raises or returns
something `live_render/3` cannot take (not a map, or a key that is not a string).

---

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