development_capture
Capture template rendering and browser console output from an open page without
losing its current state. Use this model with model#development_tools to inspect
how a page is built and verify conditional or lazy rendering.
For local and remote workflows, see the LLM site development guide.
Access and setup
Enable module#mod_development on the site. All data and mutation endpoints
require an administrator with use mod_development permission and a site
environment of development, test, or acceptance. Production is denied,
including trusted CLI calls. The console_allowed capability probe returns a
boolean rather than an access error.
Read-only administrators can list captures and read their status, traces, and logs. Attaching, starting, stopping, and submitting logs require write access. Attaching to an existing capture and submitting logs also require the browser session that created it. Session IDs are never returned.
The browser API is window.z_development_capture. Its script is included on
authorized pages when the floating template trace button is enabled. Use the
normal development head assets; the button and JavaScript use the same API.
Capture an open page
Run this in the intended page's browser console or browser execution tool:
const capture = await window.z_development_capture.attach();
// Interact with the page: scroll, open a panel, or trigger a lazy component.
await window.z_development_capture.flush();
const trace = await window.z_development_capture.trace();
const logs = await window.z_development_capture.console();
await window.z_development_capture.stop();
trace contains {capture, nodes, edges}. logs contains
{entries, truncated}; each entry has seq, text, and received_at.
Read results through the execution tool's return value rather than logging them
back into the capture. Reads do not submit or clear browser logs.
JavaScript reference
All methods return promises. Permission and transport failures reject; model
errors expose error.code. An optional ID defaults to the page's attachment.
| Method | Behavior |
|---|---|
list() | GET all available capture summaries; returns [] when none exist. |
status(captureId?) | GET a capture summary. |
trace(captureId?) | GET the accumulated template graph. |
console(captureId?) | GET stored console entries without flushing pending logs. |
attach() | Reuse the page's attachment or create one; start it if stopped. |
attach(captureId) | Validate write access and session ownership, then reconnect and start if stopped. |
start(captureId) | Start an existing capture; an ID is required. |
stop() | Flush this page's pending logs, then stop its capture. |
stop(captureId) | Stop an existing capture without flushing this page's logs. |
flush() | Submit this page's pending logs without starting or stopping its capture. |
detach() | Clear the local attachment and pending logs, restore console.log, and leave the server capture running. |
Status and evidence reads return null when the capture is missing or expired,
or when no page attachment exists. Explicit-ID start/stop also return null
for a missing capture. Explicit-ID reads and start/stop do not change the local
attachment or sessionStorage, including on errors.
attach(captureId) rejects missing, expired, or foreign-session IDs; it never
creates a replacement. Validation and start must succeed before the ID is saved.
Switching IDs discards the previous browser buffer; call flush() first to retain
it. Reconnecting the same ID preserves that buffer.
flush() returns status, or null when unattached or expired. It does not submit
to a stopped capture. detach() returns null, makes no network request, and
works offline and with read-only access. Queued local commands and late replies
cannot reattach a detached page; already-sent server requests may still complete.
Do not automatically retry mutations after a timeout.
Ownership and capture summaries
List and status results contain these fields:
| Field | Meaning |
|---|---|
id | Capture ID used by every interface. |
path | Page path recorded at attachment; tracing is session-scoped, not restricted to this path. |
state | active or stopped. |
expires | Absolute expiry time, in Unix seconds. |
truncated | A graph or server console limit has been reached. |
trace_scope | session. Other tabs in the session may contribute renders. |
is_owner | Whether this request has the browser session that created the capture. False without a session. |
write_allowed | Whether the caller may mutate captures; independent of ownership. |
console_allowed | Whether the caller may access console diagnostics. |
To find a capture this browser can reconnect to:
const captures = await window.z_development_capture.list();
const owned = captures.find(c => c.is_owner && c.write_allowed);
if (owned) {
await window.z_development_capture.attach(owned.id);
}
Read-only owners cannot attach or submit logs. Writable non-owners can start or stop captures by ID but cannot attach or submit browser logs. Server checks are authoritative; the flags help clients select the right operation.
The browser requires is_owner === true before wrapping console.log or
submitting logs. Local attach/flush/stop and background polling refresh status
first. Lost or missing ownership restores normal console output and discards
pending logs. Local mutations reject with eacces; background polling uses GET
only. Use detach() to clear a stale local attachment.
HTTP and MQTT endpoints
HTTP uses /api/model/development_capture/<method>/<path>. Browser MQTT calls
use bridge/origin/model/development_capture/<method>/<path>; a direct server
MQTT client uses model/development_capture/<method>/<path>.
| Method | Path | Payload | Result |
|---|---|---|---|
| GET | console_allowed | None | Capability boolean. |
| GET | list | None | Array of capture summaries. |
| GET | status/ID | None | Capture summary. |
| GET | trace/ID | None | {capture, nodes, edges}. |
| GET | console/ID | None | {entries, truncated}. |
| POST | attach | {path: "/example"} | New, stopped capture bound to the caller's browser session. |
| POST | start/ID | None | Active capture summary; existing evidence is retained. |
| POST | stop/ID | None | Stopped capture summary. |
| POST | poll/ID | {entries: ["message"]} | Submit logs to an active capture and return status; requires the owning session. |
HTTP JSON and MQTT response payloads use {status: "ok", result: ...} or
{status: "error", error: ...}. Raw model endpoints return enoent for missing
or expired captures; JavaScript methods translate it to null where described
above. eacces denotes denied access; invalid arguments or a foreign-session
poll can return badarg.
An OAuth2 administrator token can inspect existing captures and, with write access, start/stop them. A token without the owning browser session cannot attach to that capture or submit its console logs. The browser normally handles attachment and polling; CLI/API control alone does not collect browser output.
CLI and SSH
Run from the server's Zotonic checkout as its normal server user:
bin/zotonic capture mysite list
bin/zotonic capture mysite status CAPTURE_ID
bin/zotonic capture mysite start CAPTURE_ID
bin/zotonic capture mysite trace CAPTURE_ID
bin/zotonic capture mysite console CAPTURE_ID
bin/zotonic capture mysite stop CAPTURE_ID
For remote access, run the same commands through SSH:
ssh site-host 'cd /srv/zotonic && bin/zotonic capture mysite list'
CLI commands use trusted Erlang node RPC and still enforce the environment
restriction. They return JSON {ok: true, result: ...} or
{ok: false, error: ...}, with a nonzero exit status for failures.
Lifetime and limits
- A capture expires ten minutes after creation. Polling and restarting do not extend it. Stopping retains evidence until expiry.
- At most 16 captures exist per site. Attach a fresh capture for a fresh graph; starting an existing one is idempotent and retains evidence.
- The graph is capped at approximately 1,000 nodes and 5,000 edges. Console history retains the latest 500 entries, up to 4,096 bytes each and 50 per upload.
- Browser console collection covers subsequent
console.logcalls only. It is best effort: overflow, navigation, disconnection, or remote stopping can lose logs. - The browser polls every second. Attachment survives reload via sessionStorage; renders before attachment cannot be reconstructed. Attach before reloading to trace the initial render. Duplicated tabs may inherit the stored attachment.
- This API provides template traces and console text, not screenshots, DOM snapshots, historical console output, or browser action execution.
Existing human template tracing remains independent of capture sessions.