Flicker.Providers.Routes (Flicker v0.1.0)

Copy Markdown View Source

A Flicker.Provider that derives navigable destinations from a host's own Phoenix.Router (Spec 011).

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), 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.

Summary

Functions

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

Every destination this provider would offer, unfiltered by text.

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

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

Functions

derive_label(path)

@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(opts)

@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(values, opts)

@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 requires of every provider.

search(query, opts)

@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.