HEEx — 18 Operations for AI Agents
HEEx templates drive Phoenix LiveView, where server state renders straight to the DOM. act101 reads components, slots, and assigns as structure, so agents navigate LiveView UIs the way the framework thinks about them.
This page is the canonical reference an AI coding agent uses to refactor, query, and analyze HEEx code through the act MCP server. 18 operations available: 0 refactor, 18 query, 0 analysis. Each operation is callable from Claude Code, Cursor, Codex, OpenCode, or any MCP-compatible agent host. Click any operation for a stable anchor link suitable for citation.
Worked HEEx examples
act101 reads a HEEx template's HTML tags as tag declarations named for the tag, and its Phoenix function components (<.badge>) and named slots (<:col>) as element declarations. symbols covers the identical seven-entry set — tags, components, and slots — but reports every one under kind unknown, since the query's bare @definition capture carries no kind suffix. Neither query reads the Elixir expressions embedded in <%= %>/{ } interpolations — @batch.number, @batch.status, and the rest stay out of the declaration list, since a template's data references aren't declarations. The unit of structure in this grammar is the individual tag, component, or slot: a component's own attributes and slot bodies aren't traversed any further. Each example below is the verbatim output of the command shown, run against the file shown. Query outputs are pretty-printed with the timing block omitted.
Read the batch log's tags, components, and slots
batch_log.heex is a Phoenix LiveView template for a brewery batch log: a .badge component shows the batch status, a .table component with two :col slots lists gravity readings, and a .button logs a new reading.
$ act query skeleton batch_log.heex
Before
<div class="batch-log">
<h1>Batch <%= @batch.number %></h1>
<.badge status={@batch.status} />
<.table rows={@batch.readings}>
<:col :let={reading} label="Time">{reading.time}</:col>
<:col :let={reading} label="Gravity">{reading.gravity}</:col>
</.table>
<.button phx-click="log_reading">Log reading</.button>
</div>
Output
{
"type": "Skeleton",
"declarations": [
{
"kind": "tag",
"name": "div",
"range": {
"start": {
"file": "batch_log.heex",
"line": 1,
"column": 1,
"byte_offset": 0
},
"end": {
"file": "batch_log.heex",
"line": 9,
"column": 7,
"byte_offset": 333
}
},
"name_range": {
"start": {
"file": "batch_log.heex",
"line": 1,
"column": 2,
"byte_offset": 1
},
"end": {
"file": "batch_log.heex",
"line": 1,
"column": 5,
"byte_offset": 4
}
}
},
{
"kind": "tag",
"name": "h1",
"range": {
"start": {
"file": "batch_log.heex",
"line": 2,
"column": 3,
"byte_offset": 26
},
"end": {
"file": "batch_log.heex",
"line": 2,
"column": 38,
"byte_offset": 61
}
},
"name_range": {
"start": {
"file": "batch_log.heex",
"line": 2,
"column": 4,
"byte_offset": 27
},
"end": {
"file": "batch_log.heex",
"line": 2,
"column": 6,
"byte_offset": 29
}
}
},
{
"kind": "element",
"name": ".badge",
"range": {
"start": {
"file": "batch_log.heex",
"line": 3,
"column": 3,
"byte_offset": 64
},
"end": {
"file": "batch_log.heex",
"line": 3,
"column": 36,
"byte_offset": 97
}
},
"name_range": {
"start": {
"file": "batch_log.heex",
"line": 3,
"column": 4,
"byte_offset": 65
},
"end": {
"file": "batch_log.heex",
"line": 3,
"column": 10,
"byte_offset": 71
}
}
},
{
"kind": "element",
"name": ".table",
"range": {
"start": {
"file": "batch_log.heex",
"line": 4,
"column": 3,
"byte_offset": 100
},
"end": {
"file": "batch_log.heex",
"line": 7,
"column": 12,
"byte_offset": 269
}
},
"name_range": {
"start": {
"file": "batch_log.heex",
"line": 4,
"column": 4,
"byte_offset": 101
},
"end": {
"file": "batch_log.heex",
"line": 4,
"column": 10,
"byte_offset": 107
}
}
},
{
"kind": "element",
"name": "col",
"range": {
"start": {
"file": "batch_log.heex",
"line": 5,
"column": 5,
"byte_offset": 136
},
"end": {
"file": "batch_log.heex",
"line": 5,
"column": 60,
"byte_offset": 191
}
},
"name_range": {
"start": {
"file": "batch_log.heex",
"line": 5,
"column": 7,
"byte_offset": 138
},
"end": {
"file": "batch_log.heex",
"line": 5,
"column": 10,
"byte_offset": 141
}
}
},
{
"kind": "element",
"name": "col",
"range": {
"start": {
"file": "batch_log.heex",
"line": 6,
"column": 5,
"byte_offset": 196
},
"end": {
"file": "batch_log.heex",
"line": 6,
"column": 66,
"byte_offset": 257
}
},
"name_range": {
"start": {
"file": "batch_log.heex",
"line": 6,
"column": 7,
"byte_offset": 198
},
"end": {
"file": "batch_log.heex",
"line": 6,
"column": 10,
"byte_offset": 201
}
}
},
{
"kind": "element",
"name": ".button",
"range": {
"start": {
"file": "batch_log.heex",
"line": 8,
"column": 3,
"byte_offset": 272
},
"end": {
"file": "batch_log.heex",
"line": 8,
"column": 57,
"byte_offset": 326
}
},
"name_range": {
"start": {
"file": "batch_log.heex",
"line": 8,
"column": 4,
"byte_offset": 273
},
"end": {
"file": "batch_log.heex",
"line": 8,
"column": 11,
"byte_offset": 280
}
}
}
]
}
The skeleton reports seven declarations: div and h1 as tag, .badge, .table, and .button as element, and the two :col slots as element named col; the <%= @batch.number %> interpolation and the {@batch.status}/{reading.time}/{reading.gravity} expressions produce nothing.
List the same structure as flat symbols
The two :col slots share the same reading binding via :let, one labeled Time and the other Gravity.
$ act query symbols batch_log.heex
Before
<div class="batch-log">
<h1>Batch <%= @batch.number %></h1>
<.badge status={@batch.status} />
<.table rows={@batch.readings}>
<:col :let={reading} label="Time">{reading.time}</:col>
<:col :let={reading} label="Gravity">{reading.gravity}</:col>
</.table>
<.button phx-click="log_reading">Log reading</.button>
</div>
Output
{
"type": "Symbols",
"symbols": [
{
"name": "div",
"kind": "unknown",
"range": {
"start": {
"file": "batch_log.heex",
"line": 1,
"column": 2,
"byte_offset": 1
},
"end": {
"file": "batch_log.heex",
"line": 1,
"column": 5,
"byte_offset": 4
}
},
"visibility": "unknown"
},
{
"name": "h1",
"kind": "unknown",
"range": {
"start": {
"file": "batch_log.heex",
"line": 2,
"column": 4,
"byte_offset": 27
},
"end": {
"file": "batch_log.heex",
"line": 2,
"column": 6,
"byte_offset": 29
}
},
"visibility": "unknown"
},
{
"name": ".badge",
"kind": "unknown",
"range": {
"start": {
"file": "batch_log.heex",
"line": 3,
"column": 4,
"byte_offset": 65
},
"end": {
"file": "batch_log.heex",
"line": 3,
"column": 10,
"byte_offset": 71
}
},
"visibility": "unknown"
},
{
"name": ".table",
"kind": "unknown",
"range": {
"start": {
"file": "batch_log.heex",
"line": 4,
"column": 4,
"byte_offset": 101
},
"end": {
"file": "batch_log.heex",
"line": 4,
"column": 10,
"byte_offset": 107
}
},
"visibility": "unknown"
},
{
"name": "col",
"kind": "unknown",
"range": {
"start": {
"file": "batch_log.heex",
"line": 5,
"column": 7,
"byte_offset": 138
},
"end": {
"file": "batch_log.heex",
"line": 5,
"column": 10,
"byte_offset": 141
}
},
"visibility": "unknown"
},
{
"name": "col",
"kind": "unknown",
"range": {
"start": {
"file": "batch_log.heex",
"line": 6,
"column": 7,
"byte_offset": 198
},
"end": {
"file": "batch_log.heex",
"line": 6,
"column": 10,
"byte_offset": 201
}
},
"visibility": "unknown"
},
{
"name": ".button",
"kind": "unknown",
"range": {
"start": {
"file": "batch_log.heex",
"line": 8,
"column": 4,
"byte_offset": 273
},
"end": {
"file": "batch_log.heex",
"line": 8,
"column": 11,
"byte_offset": 280
}
},
"visibility": "unknown"
}
]
}
symbols reports the identical seven entries, but every one comes back kind unknown rather than tag or element, since the query's bare @definition capture has no kind suffix to draw a more specific kind from.
Score the one Elixir interpolation as complexity
The template's only embedded Elixir output written with <%= %> is @batch.number in the h1 heading; every other data reference uses { }.
$ act query complexity batch_log.heex
Before
<div class="batch-log">
<h1>Batch <%= @batch.number %></h1>
<.badge status={@batch.status} />
<.table rows={@batch.readings}>
<:col :let={reading} label="Time">{reading.time}</:col>
<:col :let={reading} label="Gravity">{reading.gravity}</:col>
</.table>
<.button phx-click="log_reading">Log reading</.button>
</div>
Output
{
"type": "Complexity",
"score": 2,
"details": "Base complexity: 1, +1 directive"
}
Complexity starts at a base of 1 and adds one point for the single directive node — the same <%= @batch.number %> interpolation neither the skeleton nor symbols reported as a declaration — for a total score of 2; the {@batch.status}/{reading.time}/{reading.gravity} expressions don't add anything, since they parse as a different node kind the complexity scorer doesn't count.
Query
18 query tools, the same on every supported language. Descriptions live in the shared reference: /docs/query-tools.
callers control_flow data_flow definition diagnostics effect_closure effect_summary fix_auto get_type graph import_organize interface mutations references repo_outline skeleton symbols symbols_batch