# `Xenia.Layout`
[🔗](https://github.com/uzaydurdu/xenia/blob/v0.1.1/lib/xenia/app/layout.ex#L1)

A layout in a Xenia app folder: a frame around every page in its folder
and below.

Layouts nest by folder, not by declaration. A `layout.ex` applies to every
page in its folder and below, inside the layouts of its ancestor folders.
See `Xenia.Routes.Discovery` for the folder conventions, and `Xenia.Page`
for how a page is rendered inside its layouts.

    # app/(auth)/layout.ex
    defmodule MyAppWeb.AuthLayout do
      use MyAppWeb, :html
      use Xenia.Layout, on_mount: {MyAppWeb.Auth, :require_user}

      def frame(assigns) do
        ~H"""
        <div class="flex">
          <aside>{@persistent}</aside>
          <main>{@inner_content}</main>
        </div>
        """
      end
    end

## The frame

`frame/1` is re-rendered on every navigation. It receives the page's
assigns, plus:

  * `@inner_content` - the page, already inside the layouts below this one
  * `@persistent` - this layout's persistent part, or `nil`; place it where
    it belongs

## The persistent part

A `persistent.ex` next to the `layout.ex` is the layout's persistent part:
a LiveView (`use Xenia.Persistent`) that stays mounted, with its state and
its JavaScript, while the user moves between pages under this layout. The
frame places it with `{@persistent}`. Persistent parts are rendered side
by side, never one inside another, and survive only within one
`live_session` (CLAUDE.md §9, experiment S1).

## Options

  * `:on_mount` - `on_mount` hooks run when any page under this layout
    mounts, outermost layout first, like `live_session`'s. This is where a
    route group gets its authentication: a folder name grants nothing.
    Takes a module, `{module, argument}`, or a list of them.
  * `:sitemap` - `false` leaves every page under this layout out of
    `Xenia.Sitemap`, for example pages behind a sign-in. Default `true`.

## Metadata

A layout can define `metadata/1`: the title, description and social tags
for the pages under it, which they can override (see `Xenia.Metadata`).

    def metadata(_assigns), do: %{title: "Acme", title_template: "%s · Acme"}

# `frame`

```elixir
@callback frame(assigns :: map()) :: Phoenix.LiveView.Rendered.t()
```

Renders the layout's frame around `@inner_content`, the page. `@persistent`
is this layout's persistent part, or `nil`.

# `metadata`
*optional* 

```elixir
@callback metadata(assigns :: map()) :: map()
```

Metadata for the pages under this layout (see `Xenia.Metadata`).

# `layout?`

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

Returns true if `module` is a Xenia layout.

# `on_mount`

```elixir
@spec on_mount(module()) :: [{module(), term()}]
```

Returns the `on_mount` hooks a layout declares, as `{module, argument}`.

Raises `ArgumentError` if `module` is not a Xenia layout.

# `persistent_id`

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

The DOM id of a layout's persistent part. Stable per layout, so every page
under the layout renders "the same" sticky LiveView, and LiveView keeps it
across navigation.

# `sitemap?`

```elixir
@spec sitemap?(module()) :: boolean()
```

Whether pages under the layout belong in the sitemap (its `:sitemap`
option). Raises `ArgumentError` if `module` is not a Xenia layout.

---

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