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.

MethodBehavior
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:

FieldMeaning
idCapture ID used by every interface.
pathPage path recorded at attachment; tracing is session-scoped, not restricted to this path.
stateactive or stopped.
expiresAbsolute expiry time, in Unix seconds.
truncatedA graph or server console limit has been reached.
trace_scopesession. Other tabs in the session may contribute renders.
is_ownerWhether this request has the browser session that created the capture. False without a session.
write_allowedWhether the caller may mutate captures; independent of ownership.
console_allowedWhether 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>.

MethodPathPayloadResult
GETconsole_allowedNoneCapability boolean.
GETlistNoneArray of capture summaries.
GETstatus/IDNoneCapture summary.
GETtrace/IDNone{capture, nodes, edges}.
GETconsole/IDNone{entries, truncated}.
POSTattach{path: "/example"}New, stopped capture bound to the caller's browser session.
POSTstart/IDNoneActive capture summary; existing evidence is retained.
POSTstop/IDNoneStopped capture summary.
POSTpoll/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.log calls 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.

Edit on GitHub