Controller

Browser preview

The in-app preview browser the agent drives to verify UI work without leaving the chat.

Controller has an in-app preview browser the agent can drive directly, backed by the controller-browser skill. It lets the agent open pages, read the rendered DOM, and interact with the UI it just built — so it can verify its own work instead of guessing.

What it's for

  • Verify UI work without leaving the chat — load the page the agent just changed and confirm it renders.
  • Click through a feature to check an end-to-end flow.
  • Capture a regression by opening the affected screen.

What the agent can do

Through the preview browser the agent can:

  • Open a URL.
  • Click elements and type into inputs or contenteditable editors.
  • Wait for a visible element, page text, or URL; scroll, press keys, and go back.
  • Read the rendered DOM, including visible dialogs and menus in open shadow roots.
  • Capture screenshots, select a viewport, and query recent browser errors.

Screenshot evidence and viewport

controller browser viewport mobile      # 390 × 844
controller browser viewport desktop     # 1280 × 800
controller browser screenshot           # current viewport
controller browser screenshot --full-page
controller browser viewport default     # follow the Preview pane again

Viewport settings belong to the current worktree's pane and stay in effect until reset. Full-page capture scrolls through the page, joins viewport images, then restores the original scroll position. Pages with horizontal overflow need a wider viewport. Fixed or sticky elements can appear in more than one slice. Captures over 8192 pixels on either side or 16 megapixels are rejected; PNGs are capped at 12 MiB. Transparent pages are captured against a white browser canvas. A parked pane is briefly brought into the window's paintable area during capture, then returned to its prior position.

The screenshot command prints an absolute PNG path, pixel dimensions, page URL, command ID, and the Controller session ID when available. The HTTP result uses screenshot: { path, width, height, url, fullPage, commandId, sessionId? }. The reported URL omits credentials, query strings, and fragments. Files are saved with private permissions under the system temporary directory in a pane-specific evidence folder. The agent can inspect or attach that file to its report. Captures may contain private page content; they are created only by an explicit screenshot command and are not recorded automatically.

Browser diagnostics

controller browser diagnostics             # latest 20 entries
controller browser diagnostics --limit 50  # maximum 50

The result contains timestamped console errors, network failures, and HTTP responses with status 400 or higher for this pane. Each entry has kind (console or request), timestamp, url, and message. Entries clear on navigation, stay bounded to the latest 50 of each source, and the CLI returns at most the requested number with a command ID and optional session ID. URLs lose user information, query strings, and fragments; messages redact common authorization and cookie values and are truncated. Headers and request and response bodies are never collected. Page-authored console text can still contain application-specific secrets, so review it before sharing.

Ask the agent to "open the app and check the new settings panel" and it drives the preview browser to do exactly that.

Snapshots give editable fields and overlay controls ref=eN selectors. Use those refs to target a field on a complex page. CSS selectors can cross an open shadow root with >>>, for example studio-editor >>> #textbox. An ambiguous role=textbox typing target returns an error so the agent can choose a named field or ref instead.

Deterministic interactions

Commands for one preview pane run in request order, including navigation and snapshots. click and type retry briefly while a target appears or becomes actionable. They fail after five seconds by default and report the last reason (no element matches, hidden, disabled, obscured, or detached). Use --timeout-ms to set a timeout from 0 to 15000 milliseconds. Unknown or stale snapshot refs fail immediately; take another snapshot after navigation or a major page change.

controller browser wait visible 'role=button[name="Save"]' --timeout-ms 10000
controller browser click 'role=button[name="Save"]'
controller browser wait text 'Saved successfully'
controller browser wait url '/settings'
controller browser scroll 0 600
controller browser keys Escape
controller browser back

wait visible checks visibility; wait text looks for a substring in page text, and wait url looks for a substring in the current URL. A successful click confirms that an actionable target received a native mouse gesture. Check the expected result with wait or snapshot when the page should change. back returns an error when there is no prior page in the preview history. Submitting a form through type --submit keeps its native HTTP method and body, including POST fields.

controller browser open reports a failed result when navigation is rejected, the page fails to load, the main document returns an HTTP error status, or the load times out. The agent can use that result before taking a snapshot.

Local HTTPS dev servers

If your local dev server is served over https with a self-signed certificate (mkcert not used, ad-hoc OpenSSL cert, embedded TLS terminator, etc.), Chromium will block the navigation with NET::ERR_CERT_AUTHORITY_INVALID and the agent can't click through the interstitial. Pass --insecure to controller browser open to opt into a TLS-cert bypass for that single navigation:

controller browser open https://localhost:5050 --insecure

The bypass is scoped to loopback hosts (localhost, 127.0.0.1, [::1]) in both the server-side policy and the Electron setCertificateVerifyProc handler — so an external URL with --insecure is rejected before navigation, and the bypass can never be used to talk to an arbitrary external host without cert validation. Human users running the same flow in a normal browser can keep clicking through the "Advanced → Proceed" interstitial.

Each worktree preview uses its own browser session. Cookies, localStorage, and the --insecure certificate policy in one worktree do not affect another.

Uploading files from the agent

When the page you're testing contains an <input type="file"> (single or multiple) or a drag-and-drop dropzone, the agent can attach a file without you touching the OS picker (issue #356):

controller browser set-files ref=e3 ./dist/screenshot.png
controller browser set-files "input[type=file][multiple]" ./a.pdf --path ./b.pdf

By default the file must live inside the active worktree, mirroring the file:// URL policy. To attach a file outside the worktree, pass --allow-outside; the Electron main process re-checks the path and surfaces a confirmation prompt you must approve before the bytes leave the worktree boundary.

The host enforces a 100 MiB cap per file before the bytes are buffered into memory — the page's own max-file-size only runs after the upload reaches the webview, so a multi-GB path needs to be rejected on the host side to avoid an OOM in the Electron process.

The CLI prints a per-file outcome (accepted / rejected with reason), so the agent can detect a size or accept mismatch without re-snapping the page. The native picker still works — the user can click the input themselves at any time; set-files is purely additive.

When to use the system browser instead

The preview browser is for verifying your own running app. Prefer the system browser for:

  • Authentication flows (OAuth sign-ins and the like).
  • Downloads.
  • Anything that triggers native dialogs.

These belong in a full browser with your real session and OS integration, not the embedded preview.

On this page