# `Xenia.Routes.Discovery`
[🔗](https://github.com/uzaydurdu/xenia/blob/v0.1.1/lib/xenia/app/routes/discovery.ex#L1)

Turns paths in the app directory into routes. Pure: paths in, structs out.
Only `scan/1` reads the filesystem.

Folder names follow the Next.js App Router:

  * `runbooks`   a static URL segment
  * `(auth)`     a route group: groups pages and layouts, adds nothing to
                 the URL. It grants nothing by itself; authentication stays
                 in Phoenix pipelines and `live_session`.
  * `[id]`       a dynamic segment, `:id`
  * `[...path]`  a catch-all segment, `*path`; must be the last folder
  * `_name`      a private folder: never routed, for colocated components

Four special files:

  * `page.ex`       makes its folder a route: the UI for that URL
  * `layout.ex`     frames every page in its folder and below, outermost
                    first
  * `persistent.ex` optional; the persistent part of the `layout.ex` in the
                    same folder: a LiveView that stays alive while the user
                    moves between that layout's pages. Requires the
                    `layout.ex`, whose frame places it.
  * `live.ex`       optional; one LiveView owns every page in its folder and
                    below, so switching between them is a patch that keeps
                    the process and its state. The nearest `live.ex` wins,
                    so a nested one starts a new owner. Pages without one
                    are their own LiveView.

# `files`

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

Every `page.ex`, `layout.ex`, `persistent.ex` and `live.ex` under `root`,
relative to it and sorted.
Hidden folders are skipped. `Xenia.Routes` compares this list between
compilations to notice added and removed pages.

# `page`

```elixir
@spec page(String.t(), [String.t()]) :: Xenia.Routes.Page.t()
```

The page at `file`, given the app's `layout.ex`, `persistent.ex` and
`live.ex` files (all relative paths). Other files in the list are ignored.

    iex> Xenia.Routes.Discovery.page("(auth)/runbooks/[id]/page.ex", [
    ...>   "layout.ex",
    ...>   "(auth)/layout.ex",
    ...>   "(auth)/persistent.ex"
    ...> ])
    %Xenia.Routes.Page{
      file: "(auth)/runbooks/[id]/page.ex",
      path: "/runbooks/:id",
      params: ["id"],
      groups: ["auth"],
      layouts: ["layout.ex", "(auth)/layout.ex"],
      persistent: %{"(auth)/layout.ex" => "(auth)/persistent.ex"},
      live: nil
    }

Raises `ArgumentError` for a file that isn't a `page.ex` and for invalid
folder names.

# `pages`

```elixir
@spec pages([String.t()], keyword()) :: [Xenia.Routes.Page.t()]
```

All pages in a list of relative paths, in the order Phoenix should match
them: static segments before dynamic ones, catch-alls last.

Other files, and anything inside a private `_folder`, are ignored.
Raises `ArgumentError` when two pages resolve to the same path, when
a `live.ex` owns no page, and when a `persistent.ex` has no `layout.ex`
next to it.

With `check_owners: false`, a `live.ex` that owns no page yet is allowed:
the generators use it, because an unfinished folder isn't the new file's
problem.

# `scan`

```elixir
@spec scan(Path.t()) :: [Xenia.Routes.Page.t()]
```

Reads the app folder at `root` and returns its pages, see `pages/1`.
Paths in the result are relative to `root`.

---

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