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}. 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.
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.
Ordinary visible DOM elements remain available for human spatial references, but their
generated selectors are less stable than region IDs. See
guides/agent-native-app-development.md for the complete implementation workflow.
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