Route preview automation through live owner streams - #3548
Merged
Conversation
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Replaces preview automation's replicated owner bookkeeping with live, environment-scoped desktop hosts. A host exists only while its request stream is connected; thread selection travels with each request instead of controlling host availability.
Provider sessions are pinned to one desktop runtime so multi-step browser work cannot jump between independent Electron cookie and DOM state. Pinning survives focus changes and fails over only when that host connection disappears.
Also adds durable, per-tab browser viewport sizing for users and agents, including a complete device-toolbar interaction: the preview menu reveals a Chrome-style toolbar and resize rails with responsive sizing, exact dimensions, named device presets, rotation, and a one-click return to Fill panel.
Why
Preview automation could randomly stop accepting navigation or snapshot requests after pairing, reconnecting, switching threads, or moving focus between windows. The development app was still running, but the server had either lost the valid automation host or retained one without a usable transport.
The old design also coupled automation availability to whichever thread a desktop was currently displaying. That meant a phone-originated chat could not use an otherwise healthy Mac Mini Electron runtime unless the Mini happened to have the same thread open. Thread focus is UI state, not host capability, so it should never have been an eligibility requirement.
Browser layout testing had a separate source-of-truth problem: the guest webview always inherited the preview panel's dimensions. Users could not hold a mobile or desktop breakpoint constant while resizing the app, agents had no protocol operation for selecting or verifying a viewport, and the earlier sizing UI did not provide a coherent toolbar/rail workflow. Zoom, cursor overlays, and recordings also assumed that panel bounds and browser-content bounds were identical.
What was broken before
React.useId(), which is not globally unique across Electron windows or independent mounts.reportOwnerandclearOwnercalls allowed stale mounts to erase or recreate newer ownership.ChatViewfor onlyactiveThreadRef, and the broker filtered by that thread. A capable desktop became unavailable merely because its UI was showing another conversation.How host routing works now
connectionIdlease. Stream lifetime is the source of truth for availability.threadId. The selected desktop synchronizes that thread's preview sessions and can create a hidden Electron webview even when another thread is visible.How the device-toolbar flow works now
The toolbar is derived from the tab's viewport setting, not from a separate visibility flag. Therefore an agent calling
preview_resizewith a preset or freeform size exposes the same UI, while selecting Fill hides it. UI commits are serialized per tab so fast drags, keyboard changes, exact-input edits, and MCP updates cannot settle out of order.Fixed Responsive and preset viewports remain unchanged when the right panel is resized; the outer area only changes centering or scrolling. The Electron host converts CSS-pixel targets to rendered element bounds using the tab's current zoom factor. Agent cursors, scrolling, hidden tabs, and recording canvases use the actual content rectangle rather than the outer panel rectangle.
The selected mode and dimensions live in the server's per-thread, per-tab preview snapshot. Navigation and status updates preserve them, and a
resizedevent synchronizes every connected client without activating a background tab. Browser tabs and device sizes therefore remain scoped to their own thread even when another thread is visible.The
preview_resizeMCP tool accepts fill, freeform, or preset input. It waits until the renderer has applied the requested mode and the guest reports the expectedwindow.innerWidth/window.innerHeight;preview_statusreports both the setting and measured CSS viewport.Device presets intentionally model viewport dimensions only. They do not spoof a mobile user agent, device-pixel ratio, touch input, or other device capabilities.
Remote behavior
Resulting invariants
Testing
vp check— passed with 0 errors (20 existing repository-wide warnings).vp run typecheck— passed across all 15 packages.Note
High Risk
Large cross-cutting change to MCP/WebSocket preview routing, session affinity, and Electron CDP input; breaking error-tag changes for automation callers.
Overview
Preview automation no longer uses replicated
reportOwner/clearOwnerstate. Desktops connect as environment-scoped hosts over a live stream (connected+requestevents) with a server-issuedconnectionId;focusHostonly affects tie-breaking. The broker pins each provider session to one host, routesthreadIdon each request, validates responses against the pending lease, and is provisioned once for MCP and all WebSocket sessions. Public errors shift towardPreviewAutomationNoAvailableHostError(legacy focused-owner / host-not-connected tags drop out of the union).Viewport sizing becomes server-owned per tab (
fill, freeform, presets) withpreview_resize/ WSpreviewResize,resizedevents, and status that reports setting plus measured CSS size. The web renderer addsPreviewAutomationHosts, a device toolbar and resize rails onHostedBrowserWebview, and content vs panel rects so zoom, cursors, and recordings track the guest viewport.Desktop preview gains zoom sync across navigation/webview replacement, main-frame load failures that persist until a new load, CDP key sequences (
PreviewKeyboard) with focus emulation and cleanup on interrupt, and typing via in-pageexecCommand(plusPreviewAutomationTargetNotEditableError). Dev refreshes the Mac launcher script env as fallback-only exports and retries transientt3code-dev://renderer load failures.Reviewed by Cursor Bugbot for commit ba9d8e5. Bugbot is set up for automated code reviews on this repo. Configure here.
Note
Route preview automation through live owner streams with connection-aware host tracking
PreviewAutomationBrokernow returns aStream<PreviewAutomationStreamEvent>(withconnectedandrequestevents) per connected host, and tracks connections via generatedconnectionIdvalues with sticky routing, lease expiry, and capability negotiation.resizeautomation operation end-to-end: new RPC (preview.resize),PreviewAutomationResizeInput/Resultschemas, server-sidePreviewManager.resizehandler, MCP tool, and a device toolbar UI (BrowserDeviceToolbar) with interactive resize handles (BrowserViewportResizeHandles) in the web client.focusHost(replacingreportOwner/clearOwner) for focus updates, scoped per(environmentId, clientId, connectionId)with latest-wins concurrency.PreviewManagernow types into pages via page runtime (avoidingInput.insertText), dispatches Chromium-correct key events with focus emulation and cleanup, and emits a typedPreviewAutomationTargetNotEditableErrorwhen a target is not editable.HostedBrowserWebviewviewport layout, zoom-aware scaling, scroll tracking, and content geometry publishing via a newbrowserSurfaceStorepresentContentmethod.PreviewAutomationHostNotConnectedErrorandPreviewAutomationNoFocusedOwnerErrorare removed from the public error union; callers must handlePreviewAutomationNoAvailableHostErrorandPreviewAutomationTargetNotEditableErrorinstead.Macroscope summarized ba9d8e5.