fix(preview): closing a desktop preview tab lets its page unload first #35967
Openshivamhwp(GitHub profile, opens in a new tab)wants to merge 6 commits into
main fromrefs/pull/35967/headopened View on GitHubgithub-actions[bot](GitHub profile, opens in a new tab) added labelsvouch:trusted
github-actions[bot](GitHub profile, opens in a new tab) added labelssize:L
- apps/web/src/previewStateStore.ts line 438Outdated@@ -430,3 +430,9 @@430430 });431431 }432432433+/** Removes a closed tab; a stale snapshot cannot restore it until its closed event lands. */434+export function finishPreviewSessionClose(ref: ScopedThreadRef, tabId: string): void {435+ updateThreadPreviewState(ref, (current) => {436+ const closed = removeSession(current, tabId);437+ if (closed.suppressedTabIds.has(tabId)) return closed;438+ return { ...closed, suppressedTabIds: new Set(closed.suppressedTabIds).add(tabId) };
macroscopeapp[bot](GitHub profile, opens in a new tab) approved these changes
tanstack-source added labelsbug
- apps/server/src/preview/ServerBrowser.ts line 1633Outdated@@ -1590,2 +1614,20 @@1590- assertTabCapacity(input.automationOwner);1614+ Effect.tryPromise({1615+ try: async () => {1616+ const agentId = input.automationOwner;1617+ const reclaim =1618+ agentId !== undefined &&1619+ options.reclaimIdleSlot === true &&1620+ serverAtTabLimit() &&1621+ !agentAtTabLimit(agentId);1622+ if (agentId !== undefined && !reclaim) assertTabCapacity(agentId);1623+ // Reserved before any tab closes for it, so a concurrent open sees the agent's budget1624+ // taken; the server holds one extra reservation until the reclaimed tab leaves.1625+ pendingTabs.set(key, reservation);1626+ if (!reclaim) return;1627+ let freed = false;1628+ try {1629+ freed = await freeIdleAgentSlot();1630+ } finally {1631+ if (!freed) pendingTabs.delete(key);15911632 }1633+ if (freed) return;
macroscopeapp[bot](GitHub profile, opens in a new tab) approved these changes
- apps/server/src/preview/ServerBrowser.test.ts lines 1589 to 1593Outdated@@ -1551,0 +1573,21 @@1573+ // The desktop removes the webview once the session closes, dropping an unload in progress.1574+ const events = yield* manager.subscribeEvents;1575+ const sessionClosed = Promise.withResolvers<void>();1576+ yield* Effect.gen(function* () {1577+ while ((yield* PubSub.take(events)).type !== "closed");1578+ page.removed = true;1579+ sessionClosed.resolve();1580+ }).pipe(Effect.forkScoped);1581+ page.beforeUnload = "listen";1582+ page.unloadGate = Promise.withResolvers<void>();1583+ const closing = yield* (1584+ closer === "agent"1585+ ? broker.invoke({ scope, tabId, operation: "close", input: {} })1586+ : serverBrowser.close({ threadId: scope.thread.threadId, tabId })1587+ ).pipe(Effect.forkScoped);1588+ // The page finishes unloading once the server listens for it, or too late if it never does.1589+ yield* Effect.promise(() =>1590+ Promise.race([page.pagehideWatched.promise, sessionClosed.promise]),1591+ );1592+ expect(browser.close).not.toHaveBeenCalled();1593+ page.unloadGate.resolve();apps/server/src/preview/ServerBrowser.ts line 3271Outdated@@ -3202,0 +3251,21 @@3251+ const grace = Promise.withResolvers<void>();3252+ const timeout = setTimeout(grace.resolve, PAGE_CLOSE_GRACE_MS);3253+ await Promise.race([Promise.all(settled), grace.promise]);3254+ clearTimeout(timeout);3255+ for (const key of keys) if (attaching(key)) closedPendingTabs.add(key);3256+ };3257+3258+ // Uninterruptible: stopping after the unload starts would leave a closing tab open.3259+ const close = Effect.fn("ServerBrowser.close")(function* (input: PreviewCloseInput) {3260+ const closes = (key: string) =>3261+ input.tabId === undefined3262+ ? key.startsWith(tabKey(input.threadId, ""))3263+ : key === tabKey(input.threadId, input.tabId);3264+ yield* Effect.promise(async () => {3265+ await awaitAttaching([...pendingTabs.keys()].filter(closes));3266+ const closing = [...tabs.entries()].flatMap(([key, tab]) =>3267+ tab.desktop !== null && closes(key) ? [tab] : [],3268+ );3269+ await Promise.all(closing.map(unloadDesktopTab));3270+ });3271+ yield* manager.close(input);
macroscopeapp[bot](GitHub profile, opens in a new tab) approved these changes
Sign in to join this conversation.
macroscopeapp[bot](GitHub profile, opens in a new tab) reviewed