# `Flicker.Providers.Routes`
[🔗](https://github.com/team-alembic/flicker/blob/v0.1.0/lib/flicker/providers/routes.ex#L1)

A `Flicker.Provider` that derives navigable destinations from a host's own
`Phoenix.Router` ([Spec 011](https://github.com/team-alembic/flicker/blob/main/docs/specs/spec-011-router-navigation-provider.md)).

This is the other half of the ⌘K experience: a palette that can take you to a
*screen*, not only to a record, with one line of config and nothing to keep in
sync when a route changes.

    <Flicker.palette source={{Flicker.Providers.Routes, router: MyAppWeb.Router}} />

Results carry `meta.href`, which is Spec 008's navigate-on-select convention,
and `group: "Pages"` so they read as a section when federated alongside
records.

## Authorisation: this provider is not policy-scoped

**A router knows paths, not permissions.** Every other Flicker provider reads
through `actor:` and Ash policies
([ADR-004](https://github.com/team-alembic/flicker/blob/main/docs/adrs/adr-004-authorization-via-actor-and-policies.md)),
and this one structurally cannot: a route is a compile-time fact with nothing
to authorise against. It lists what it is given.

That makes the default deliberately conservative — only `GET`/`live` routes,
no parameterised paths, and framework mounts excluded — and it makes
`:visible?` the mechanism that matters. Pass `visible?: fn route, actor ->
boolean end` to gate destinations for the current actor, and treat any
sensitive path as needing that gate rather than assuming the search hid it.
A palette entry is not an authorisation boundary in any case: it reveals that
a path exists, and the page behind it must still refuse.

## Options

  * `:router` — required, the `Phoenix.Router` module to introspect.
  * `:only` — path prefixes to include (list of strings). Everything else is
    dropped.
  * `:except` — path prefixes to exclude. Applied after `:only`.
  * `:visible?` — `fn route, actor -> boolean end`, the authorisation hook
    described above.
  * `:label` — `fn route -> String.t() end`, overriding the derived label.
  * `:group` — the group label. Defaults to `"Pages"`; `nil` opts out of
    grouping.
  * `:extra` — additional destinations the router can't supply, as
    `[%{label: ..., href: ...}]`. This is where a parameterised favourite
    ("My profile" → `/users/42`) belongs.

## Label derivation

Dumb but predictable: path segments are humanised and joined with `·`, so
`/user-settings/billing` becomes "User settings · Billing" and `/` becomes
"Home". Where that reads badly, `:label` overrides it — the derivation never
tries to be clever, because a surprising label is worse than a plain one.

# `derive_label`

```elixir
@spec derive_label(String.t()) :: String.t()
```

The label derived from a path — humanised segments joined with `·`.

## Examples

    iex> Flicker.Providers.Routes.derive_label("/")
    "Home"

    iex> Flicker.Providers.Routes.derive_label("/user-settings/billing")
    "User settings · Billing"

    iex> Flicker.Providers.Routes.derive_label("/artists")
    "Artists"

# `destinations`

```elixir
@spec destinations(keyword()) :: [Flicker.Result.t()]
```

Every destination this provider would offer, unfiltered by text.

Exposed so a host can inspect what a given configuration actually exposes —
worth doing once for anything sensitive, given the authorisation caveat above.

## Examples

    iex> Flicker.Providers.Routes.destinations(router: Flicker.Test.Router)
    ...> |> Enum.all?(&(&1.meta.href =~ "/"))
    true

# `fetch`

```elixir
@spec fetch([term()], keyword()) :: {:ok, [Flicker.Result.t()]} | {:error, term()}
```

Resolves hrefs back to results, so a palette restoring state from a URL shows
the destination's label rather than its path.

Values not present are simply absent, as
[ADR-003](https://github.com/team-alembic/flicker/blob/main/docs/adrs/adr-003-fetch-takes-a-list.md)
requires of every provider.

# `search`

```elixir
@spec search(Flicker.Query.t(), keyword()) ::
  {:ok, [Flicker.Result.t()]} | {:error, term()}
```

Returns routes whose label matches `query.text`, case-insensitively.

`query.facets` is ignored: a route has no attributes to facet on. Free text is
matched against the derived (or overridden) label, not the raw path, since the
label is what the user sees.

---

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