fix(preview): resize hidden browser tabs reliably - #8469
Conversation
|
Important Review skippedAuto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Repository UI Review profile: CHILL Plan: Pro Plus Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
Comment |
Thread transfer impact✅ Thread transfer remains within every enforced ceiling.
Baseline: Scenario and decoded snapshot size10 historical turns, 5 command tools per turn, 878.9 KiB retained MCP result per historical turn, and a 1.05 MiB retained result in the measured turn.
Updated in place by a trusted workflow. PR artifacts are strictly validated and never executed. |
ApprovabilityVerdict: Not approved Macroscope's review found this PR not approvable — This PR introduces a substantial native CDP viewport path across desktop, server, and web, including lifecycle replay, queue deadlines, and rollback behavior. Failure cases can leave the server/React viewport out of sync with the hidden guest after CDP rejection or DevTools transitions. Not approved because:
Adjust the Minimum Blocking Severity for this repo — including turning it Off — in Settings. You can add or adjust custom eligibility rules. Learn more. |
64bee3d to
e8ad8c7
Compare
8a03158 to
b20a0de
Compare
26b965d to
ba7f44d
Compare
ba7f44d to
c59ea4c
Compare
a9490b9 to
d28a5f8
Compare
preview_resize only updated the CSS/React chrome. If the browser panel was hidden, the guest never changed size and wait timed out. Resize now persists the setting, then applies a CDP device-metrics override so the guest viewport changes even when the tab is not visible.
A size-only ready check could resolve resize before React committed the webview geometry. A failed CDP apply also left the persisted snapshot on the new size. Resize now waits for the existing ready gates, rolls the snapshot back when the override fails, and re-applies the committed setting when the guest attaches.
The restore effect depended on the viewport object, so every preview event re-sent CDP and flashed the agent-controlling badge. Toolbar resizes now use a setViewport path that does not take agent control, and the effect keys only on the viewport size string.
Width-only mobile detection treated 844x390 phones as desktop. The guest also stayed pinned to the last committed size while the frame was dragged. mobile now uses the shortest side. HostedBrowserWebview applies a frame-throttled CDP override from the effective (including drag) viewport.
The HostedBrowserWebview override ran one frame after mount, before registerWebview finished, then swallowed the failure. Crash recovery and mini-player never retried. The override now runs after a successful register, and drag updates wait for hasWebContents.
PreviewView reapplied the committed size after the webview owner already set the live override. That raced drags and keyboard resizes. The active-tab effect is gone; HostedBrowserWebview is the single owner.
Device-metrics overrides live on the CDP session, so DevTools and webview swaps dropped a fixed viewport while the toolbar still showed it. Remember the last override and re-apply it from restoreControlSession, like color scheme. Guest rollback now rechecks runtime/epoch and only persists previous if the guest actually rolled back, so a replaced tab or a failed CDP clear cannot desync store and guest.
setDeviceMetricsOverride is a widget DIP size, so page zoom still divides it. Desktop presets now send setting * zoom so innerWidth matches the toolbar. Phone sizes stay unscaled because mobile emulation pins zoom to 1.
Apply-failure never put the new override on the guest, so the store still rolls back even if the guest CDP clear throws. Timeout still rolls the guest first and only persists previous if that apply succeeds; a failed store resize puts the guest back on the requested size.
1e6deb6 to
9425d6e
Compare
| const setViewport = previewBridge?.setViewport; | ||
| if (!setViewport || !hasWebContents) return; | ||
| const frame = window.requestAnimationFrame(() => { | ||
| void applyPreviewGuestViewport(setViewport, runtimeTabId, guestViewportRef.current).catch( |
There was a problem hiding this comment.
🟡 Medium browser/HostedBrowserWebview.tsx:220
When setViewport rejects after a server-side resize, the UI/server remain at the new effectiveViewport while the native guest stays at its previous size. The effect catches the rejection with () => undefined, and because neither guestViewportKey nor hasWebContents changes, there is no retry or rollback; please retry the native update or restore the prior viewport on failure.
🚀 Reply "fix it for me" or copy this AI Prompt for your agent:
In file @apps/web/src/browser/HostedBrowserWebview.tsx around line 220:
When `setViewport` rejects after a server-side resize, the UI/server remain at the new `effectiveViewport` while the native guest stays at its previous size. The effect catches the rejection with `() => undefined`, and because neither `guestViewportKey` nor `hasWebContents` changes, there is no retry or rollback; please retry the native update or restore the prior viewport on failure.
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes using high effort and found 1 potential issue.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 9425d6e. Configure here.
| ).pipe( | ||
| Effect.tap(() => acceptViewportOverride(prepared.intent)), | ||
| Effect.onError(() => rollbackViewportOverride(tabId, prepared.intent, prepared.generation)), | ||
| ); |
There was a problem hiding this comment.
Guest rollback fights server viewport
Medium Severity
Human resizes persist the new viewport on the server and in the React layout first, then call setViewport. On CDP failure (DevTools open, debugger attach, transient guest error) desktop marks that intent rejected and restores the previous native metrics, while the renderer swallows the error and does not retry. After DevTools close, restoreControlSession reapplies the rolled-back intent, so the chrome shows the new size and the page stays on the old override until another resize or zoom.
Additional Locations (1)
Reviewed by Cursor Bugbot for commit 9425d6e. Configure here.

preview_resizecould update the server and React layout while a hidden desktop webview kept its old native viewport. Failed or timed-out resizes could also leave the server, client store, and guest on different viewport values.This fixes #3712. Desktop now applies the logical viewport through CDP while the preview is hidden, scales desktop dimensions for page zoom, and clears native metrics in fill mode. The client uses server revisions and guarded rollback writes so a late response cannot replace a newer viewport. Ordered revisions take priority over wall-clock timestamps, including when the server clock moves backward.
The rebase preserves the current hidden-preview activity lease, recording state, and macOS paintability behavior. Guilherme Barros's original commits from #7303 keep their authorship in this branch.
Tests:
vp test run packages/contracts/src/preview.test.ts apps/server/src/preview/Manager.test.ts apps/web/src/browser/browserViewportActions.test.ts apps/web/src/components/preview/previewGuestViewport.test.ts apps/web/src/components/preview/previewViewportReadiness.test.ts apps/web/src/components/preview/previewViewportRollback.test.ts apps/web/src/components/preview/previewAutomationOpenReadiness.test.ts apps/web/src/components/preview/previewAutomationTarget.test.ts apps/web/src/previewStateStore.test.ts apps/desktop/src/preview/Manager.test.ts(206 passed)vp run --filter @t3tools/contracts typecheckvp run --filter t3 typecheckvp run --filter @t3tools/web typecheckvp run --filter @t3tools/desktop typecheckgit diff --checkA manual Electron pass is still needed before merge. This audit did not use browser or computer control.
Made with GPT-5.6 Sol using Codex.
Note
Medium Risk
Large cross-layer concurrency and a breaking
preview.resizesuccess shape (PreviewResizeResult); behavior is heavily tested but still needs manual Electron validation.Overview
Fixes preview resizes that updated server/React layout while a hidden desktop webview kept stale native dimensions, and reduces drift after failed or racing resize operations.
Desktop adds human
setViewportand agentautomationSetViewportIPC paths that apply logical sizes via CDPEmulation.setDeviceMetricsOverride(scaled for tab zoom, cleared in fill mode), persist intent across webview swaps/DevTools, and roll back or reconcile when applies fail or overlap.Server
preview.resizenow returnsPreviewResizeResultwithstateVersionandpreviousViewport, and rejects stale rollback writes viaPreviewResizeConflictErrorwhenexpectedStateVersiondoes not match the tab’s viewport revision.Web tracks per-tab server revisions in
previewStateStore, serializes viewport mutations with absolute deadlines so timed-out commits cannot block newer ones, syncs the guest throughapplyPreviewGuestViewporton webview attach and viewport changes, and on automation resize failure runs server-first guarded rollback before touching the guest CDP override.Reviewed by Cursor Bugbot for commit 9425d6e. Bugbot is set up for automated code reviews on this repo. Configure here.
Note
Fix preview automation viewport resize with per-tab revisions and deadline-bound mutations
previewStateStoreso stale resize responses and authoritative lists can no longer overwrite newer tab state, even when wall-clock timestamps are out of orderPreviewManager.resize: requests carry an optionalexpectedStateVersion(epoch + revision); a mismatch returnsPreviewResizeConflictErrorwithout mutating state or emitting events, and successful writes return the new version and previous viewport for rollbackqueueBrowserViewportMutation), preview automation polling (waitForRenderedViewport), and visible commits (commitBrowserViewportChange); a front-of-queue mutation that hits its deadline releases later mutations instead of blocking themPREVIEW_SET_VIEWPORT_CHANNEL,PREVIEW_AUTOMATION_SET_VIEWPORT_CHANNEL) andapplyPreviewGuestViewportto push the authoritative stored viewport to the guest after webview attachment; fill mode maps to a clear request and fixed dimensions map to explicit width/heightDEFAULT_PREVIEW_AUTOMATION_VIEWPORTinstead of inheriting a fill setting, andneedsPreviewAutomationSessionSyncforces a server refresh when the localserverEpochis nullPreviewManager.resizenow returnsPreviewResizeResultinstead ofPreviewSessionSnapshot;rpc.WsPreviewResizeRpcandipc.EnvironmentApi.preview.resizeare updated to match. Older servers withoutstateVersion/previousViewportfields remain backward-compatible since both are optional in the decoder.commitBrowserViewportChangenow enforces a 15-second deadline from invocation time including queue wait.Macroscope summarized 9425d6e.