The behaviour and macro for Dialup page modules.
A page module handles a single route. Place it at the appropriate path under your
app_dir and it will be routed automatically:
# lib/app/page.ex → /
# lib/app/blog/[slug]/page.ex → /blog/:slugUsage
defmodule MyApp.App.Page do
use Dialup.Page
def mount(_params, assigns) do
{:ok, Map.put(assigns, :count, 0)}
end
def handle_event("increment", _, assigns) do
{:update, Map.update!(assigns, :count, &(&1 + 1))}
end
def render(assigns) do
~H"""
<h1>Count: {@count}</h1>
<button ws-event="increment">+1</button>
"""
end
endCallbacks
mount/2— called on every page navigation. Receives URL params and the current assigns (which already include session data set by layouts). Return{:ok, new_assigns}or{:redirect, path}/{:redirect, path, assigns}to navigate before render. Definingmount/1(assigns only) is also accepted as a convenience.render/1— renders the page HTML using HEEx.handle_event/3— called when aws-event,ws-submit, orws-changefires.handle_info/2— called for Erlang process messages (e.g. PubSub, timers).page_title/1— optional; return a string to set<title>. Returnnilto use the application default.agent_state/1— returns the allowlisted state projection visible to an attached agent.agent_message/1— explains the page concept, operating flow, and safety constraints to an agent that has no source-code context.agent_grant/1— defines capabilities, projections, expiry, and version requirements for HTTP MCP session tokens. The default grant is read-only ([:read_scene]); override explicitly for mutating or built-in tools such aslock_ui.
Human and agent projections
Use <.dialup_action> instead of a plain event button when the same operation should be
available to an attached agent. Use <.dialup_region> for stable domain areas that need
a semantic name, structured data, or action relationships.
<.dialup_action> is the single boundary for what an agent can do: raw ws-event,
ws-submit, ws-change, and ws-href elements are never auto-exposed as tools. To make a
navigation link agent-operable, declare it with <.dialup_action navigate="/path">; the same
declaration renders the human link and generates the navigation tool.
Action modes
Each <.dialup_action> uses exactly one mode:
| Mode | Attribute | Routing |
|---|---|---|
command | command={{Context, :cmd}} | Build Commanded command → Context.dispatch/1 → remount |
set | set={%{key: value}} | Merge rendered map into assigns (inline HEEx only) |
navigate | navigate="/path" | Navigate to declared path |
action | name={:event} | Legacy handle_event/3 |
See guides/agent-native-app-development.md for Commanded integration and availability derivation.
Return values for handle_event/3 and handle_info/2
| Return value | Effect |
|---|---|
{:noreply, assigns} | Update state, no re-render |
{:update, assigns} | Re-render the full page |
{:patch, id, html, assigns} | Replace only the element with the given id |
{:redirect, path, assigns} | Navigate to another page (session is preserved) |
{:push_event, name, payload, assigns} | Call a JS hook function and re-render |
Module attributes
@layout false— disable all layout wrapping (useful for login/fullscreen pages).@static true— serve the page without establishing a WebSocket connection.
Colocation CSS
Place a page.css file in the same directory as page.ex. It is automatically scoped
to the page at compile time (no build tool required).
Helpers
use Dialup.Page imports overwrite/2, set_default/2, subscribe/2,
declare_action/1, declare_region/1, dialup_action/1, and dialup_region/1.
Summary
Functions
Declares an action that is available to both the browser UI and an attached agent.
Declares a semantic region that an attached agent can reference.
Renders an action button and serializes extra component attributes as event parameters.
Wraps content in a semantic region that agents can read through read_scene.
Derives the canonical navigation action name for an app path.
Merges overwrite_map into assigns, replacing any existing keys.
Merges defaults into assigns, keeping existing values for keys that are already set.
Subscribes to a Phoenix.PubSub topic and registers it for automatic unsubscription
on page navigation.
Callbacks
Functions
Declares an action that is available to both the browser UI and an attached agent.
declare_action name: :increment,
desc: "Increment the counter",
params: %{amount: {:integer, default: 1}}
Declares a semantic region that an attached agent can reference.
Renders an action button and serializes extra component attributes as event parameters.
<.dialup_action name={:add_item} available={@status == :draft} sku="SKU-1" qty="1">
Add item
</.dialup_action>Pass navigate to render a navigation link instead of an event button. The same
declaration becomes a navigation tool for an attached agent, so the agent can move
between pages exactly where a human can click:
<.dialup_action navigate="/docs/concepts">Concepts</.dialup_action>Navigation actions take no parameters; the destination is fixed at the declaration
site. When name is omitted it is derived from the path (e.g. /docs/concepts
becomes :navigate_docs__concepts).
Wraps content in a semantic region that agents can read through read_scene.
Merges overwrite_map into assigns, replacing any existing keys.
assigns |> overwrite(%{user: user, loaded: true})
Merges defaults into assigns, keeping existing values for keys that are already set.
assigns |> set_default(%{count: 0, page: 1})
Subscribes to a Phoenix.PubSub topic and registers it for automatic unsubscription
on page navigation.
Call this inside mount/2 to ensure the subscription is cleaned up when the user
navigates away.
def mount(_params, assigns) do
subscribe(MyApp.PubSub, "room:lobby")
{:ok, %{messages: []}}
end