# Phoenix LiveView
LiveView is a strong fit for search interfaces, but the recommended Scrypath boundary stays the same: LiveView owns UI state, and the context owns repo access plus Scrypath orchestration. Use `Scrypath.Phoenix` only as request-edge glue around params, form projection, and URL round-tripping. For the shared contract, read [Request-edge search](request-edge-search.md).
## Handle Params Through The Context
```elixir
defmodule MyAppWeb.PostLive do
use MyAppWeb, :live_view
alias MyApp.Content
alias Scrypath.Phoenix, as: SearchPhoenix
alias Scrypath.QueryParams
def mount(_params, _session, socket) do
{:ok, assign(socket, posts: [], search: nil, query: "", form: nil)}
end
def handle_params(params, _uri, socket) do
case SearchPhoenix.from_params(params) do
{:ok, query_params} ->
form = SearchPhoenix.to_form_data(query_params)
{query, search_opts} = QueryParams.to_search_args(query_params)
{:ok, result} = Content.search_posts(query, Keyword.put(search_opts, :preload, [:author]))
{:noreply,
assign(socket,
posts: result.records,
search: result,
query: query,
form: form
)}
{:error, error_map} ->
form = SearchPhoenix.to_form_data(params, error_map)
{:noreply,
assign(socket,
posts: [],
search: nil,
query: form.values["q"],
form: form
)}
end
end
end
```
## Keep UI State Local
LiveView should own:
- current query text
- filter and sort params that belong in the URL
- loading state
- selected ids and pagination state
The context should own:
- repo-backed hydration
- backend selection
- `Scrypath.search/3` options
- write-path sync and delete orchestration
`handle_event/3` should collect intent and push the next URL state. `handle_params/3` remains the one place that normalizes params, assigns attempted values plus errors, and calls the context when normalization succeeds.
Helpers normalize params/forms/URLs only, contexts remain canonical, and Phoenix is optional.
The same context boundary can back a publish event:
```elixir
def handle_event("publish", %{"id" => id, "post" => attrs}, socket) do
post = load_post!(id)
{:ok, _post} = Content.publish_post(post, attrs)
{:noreply, socket}
end
```
Keep `attrs` string-keyed at this boundary. That matches the nested params LiveView receives from the browser before your context decides how to validate or persist them.
## Keep Visibility Wording Precise
If a LiveView event updates a record and then triggers sync work, the UI should not imply immediate search visibility unless the context chose `:inline` and waited for terminal backend success.
Even then, backend acceptance and visibility semantics remain separate concerns. Keep that distinction explicit in operator-facing UI and docs.
## See also
For faceted catalogs (checkbox groups, chips, numeric ranges, and URL-synced `handle_params/3` with `Scrypath.search/3`), read `guides/faceted-search-with-phoenix-liveview.md`.