Skip to content

Commit 33fdea1

Browse files
committed
fix: prevent agent resize from switching active tab and serialize toolbar toggle viewport changes
Bug 1: applyPreviewServerSnapshot always set activeTabId to the incoming snapshot's tabId. When the automation host applied a resize result or synced sessions from listPreviews, this would switch the user's visible preview tab. Add a preserveActiveTab option and use it in the automation host's resize and session-sync paths, matching the behavior of the resized server event which deliberately preserves activeTabId. Bug 2: handleToggleDeviceToolbar called handleViewportChange directly, bypassing the commitBrowserViewportChange queue that serializes per-tab viewport commits. Route it through commitBrowserViewportChange so it cannot race with concurrent drag or toolbar resize operations.
1 parent 06221cb commit 33fdea1

3 files changed

Lines changed: 20 additions & 9 deletions

File tree

apps/web/src/components/preview/PreviewAutomationHosts.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -278,7 +278,7 @@ function PreviewAutomationHost(props: { readonly environmentId: EnvironmentId })
278278
throw squashAtomCommandFailure(result);
279279
}
280280
for (const snapshot of result.value.sessions) {
281-
applyPreviewServerSnapshot(threadRef, snapshot);
281+
applyPreviewServerSnapshot(threadRef, snapshot, { preserveActiveTab: true });
282282
}
283283
state = readThreadPreviewState(threadRef);
284284
}
@@ -374,7 +374,7 @@ function PreviewAutomationHost(props: { readonly environmentId: EnvironmentId })
374374
if (result._tag === "Failure") {
375375
throw squashAtomCommandFailure(result);
376376
}
377-
applyPreviewServerSnapshot(threadRef, result.value);
377+
applyPreviewServerSnapshot(threadRef, result.value, { preserveActiveTab: true });
378378
const viewport = await waitForRenderedViewport(
379379
ready.tabId,
380380
setting,

apps/web/src/components/preview/PreviewView.tsx

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,10 @@ import { PreviewChromeRow } from "./PreviewChromeRow";
2929
import { formatPreviewUrl } from "./previewUrlPresentation";
3030
import { PreviewEmptyState } from "./PreviewEmptyState";
3131
import { PreviewMoreMenu } from "./PreviewMoreMenu";
32-
import { subscribeBrowserViewportChange } from "~/browser/browserViewportActions";
32+
import {
33+
commitBrowserViewportChange,
34+
subscribeBrowserViewportChange,
35+
} from "~/browser/browserViewportActions";
3336
import { resolveResponsiveBrowserViewportSize } from "~/browser/browserViewportLayout";
3437
import { PreviewUnreachable } from "./PreviewUnreachable";
3538
import { revealInFileExplorerLabel } from "./fileExplorerLabel";
@@ -174,15 +177,18 @@ export function PreviewView({ threadRef, tabId: requestedTabId, configuredUrls,
174177
);
175178

176179
const handleToggleDeviceToolbar = () => {
180+
if (!tabId) return;
177181
if (viewport._tag !== "fill") {
178-
void handleViewportChange(FILL_PREVIEW_VIEWPORT).catch(() => undefined);
182+
void commitBrowserViewportChange(tabId, FILL_PREVIEW_VIEWPORT).catch(() => undefined);
179183
return;
180184
}
181185

182186
const responsiveSize = panelRect
183187
? resolveResponsiveBrowserViewportSize(panelRect, desktopOverlay?.zoomFactor)
184188
: { width: 1024, height: 768 };
185-
void handleViewportChange({ _tag: "freeform", ...responsiveSize }).catch(() => undefined);
189+
void commitBrowserViewportChange(tabId, { _tag: "freeform", ...responsiveSize }).catch(
190+
() => undefined,
191+
);
186192
};
187193

188194
useEffect(() => {

apps/web/src/previewStateStore.ts

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -213,6 +213,7 @@ export function applyPreviewServerEvent(ref: ScopedThreadRef, event: PreviewEven
213213
export function applyPreviewServerSnapshot(
214214
ref: ScopedThreadRef,
215215
snapshot: PreviewSessionSnapshot | null,
216+
options?: { preserveActiveTab?: boolean },
216217
): void {
217218
updateThreadPreviewState(ref, (current) => {
218219
if (!snapshot && current.snapshot === null) return current;
@@ -233,12 +234,16 @@ export function applyPreviewServerSnapshot(
233234
snapshot.navStatus._tag !== "Idle"
234235
? dedupeRecentUrls(current.recentlySeenUrls, snapshot.navStatus.url)
235236
: current.recentlySeenUrls;
237+
const sessions = { ...current.sessions, [snapshot.tabId]: snapshot };
238+
const keepActive = options?.preserveActiveTab && current.activeTabId !== null;
239+
const activeTabId = keepActive ? current.activeTabId : snapshot.tabId;
240+
const activeSnapshot = sessions[activeTabId!] ?? snapshot;
236241
return {
237242
...current,
238-
snapshot,
239-
sessions: { ...current.sessions, [snapshot.tabId]: snapshot },
240-
activeTabId: snapshot.tabId,
241-
desktopOverlay: current.desktopByTabId[snapshot.tabId] ?? null,
243+
snapshot: activeSnapshot,
244+
sessions,
245+
activeTabId,
246+
desktopOverlay: current.desktopByTabId[activeTabId!] ?? null,
242247
recentlySeenUrls,
243248
};
244249
});

0 commit comments

Comments
 (0)