mirror of
https://github.com/stablyai/orca.git
synced 2026-10-20 08:03:28 +00:00
fix(browser): keep browser tabs rendering across worktree switches (STA-3228)
Switching away from a worktree that had a targeted background mount unmounted BrowserPaneOverlayLayer, pulling the persistent <webview> slots out of the DOM and killing their guests; the stale viewport cache then kept rendering into the removed subtree, so the tab stayed blank forever and reload threw. Keep the overlay mounted for hidden worktrees (slots park their panes, so this stays cheap) and rebuild cached viewports whose slot root remounted.
This commit is contained in:
@@ -2674,11 +2674,10 @@ const WorktreeSplitSurface = React.memo(function WorktreeSplitSurface({
|
||||
backgroundMountTabIds={backgroundMountTabIds}
|
||||
activationDeferredMountTabIds={activationDeferredMountTabIds}
|
||||
/>
|
||||
{/* Why: always mounted — its slots host persistent <webview> guests that die if the slot leaves the DOM (STA-3228); hidden worktrees park the pane subtree, so targeted background mounts only pay for empty slot divs. */}
|
||||
<BrowserPaneOverlayLayer worktreeId={worktreeId} isWorktreeActive={isVisible} />
|
||||
{isVisible || backgroundMountTabIds === null ? (
|
||||
<>
|
||||
<BrowserPaneOverlayLayer worktreeId={worktreeId} isWorktreeActive={isVisible} />
|
||||
<EmulatorPaneOverlayLayer worktreeId={worktreeId} isWorktreeActive={isVisible} />
|
||||
</>
|
||||
<EmulatorPaneOverlayLayer worktreeId={worktreeId} isWorktreeActive={isVisible} />
|
||||
) : null}
|
||||
<AiVaultSessionDropLayer worktreeId={worktreeId} enabled={isVisible} />
|
||||
</div>
|
||||
|
||||
@@ -46,6 +46,41 @@ describe('ensureBrowserPageViewport', () => {
|
||||
it('returns null until the slot viewport root is registered', () => {
|
||||
expect(ensureBrowserPageViewport('page-1', 'workspace-missing')).toBeNull()
|
||||
})
|
||||
|
||||
it('reuses the cached viewport while the slot root is unchanged', () => {
|
||||
mountSlotViewport('workspace-1')
|
||||
const first = ensureBrowserPageViewport('page-1', 'workspace-1')
|
||||
const second = ensureBrowserPageViewport('page-1', 'workspace-1')
|
||||
|
||||
expect(second).toBe(first)
|
||||
})
|
||||
|
||||
it('keeps the parked viewport while the slot root is unregistered', () => {
|
||||
const root = mountSlotViewport('workspace-1')
|
||||
const parked = ensureBrowserPageViewport('page-1', 'workspace-1')
|
||||
root.remove()
|
||||
registerBrowserOverlaySlotViewport('workspace-1', null)
|
||||
|
||||
expect(ensureBrowserPageViewport('page-1', 'workspace-1')).toBe(parked)
|
||||
})
|
||||
|
||||
it('rebuilds under a replacement slot root instead of returning the stranded shell (STA-3228)', () => {
|
||||
const oldRoot = mountSlotViewport('workspace-1')
|
||||
const stale = ensureBrowserPageViewport('page-1', 'workspace-1')!
|
||||
// Worktree overlay unmounts while hidden: slot root leaves the DOM with the shell inside.
|
||||
oldRoot.remove()
|
||||
registerBrowserOverlaySlotViewport('workspace-1', null)
|
||||
const newRoot = mountSlotViewport('workspace-1')
|
||||
|
||||
const rebuilt = ensureBrowserPageViewport('page-1', 'workspace-1')
|
||||
|
||||
expect(rebuilt).not.toBeNull()
|
||||
expect(rebuilt).not.toBe(stale)
|
||||
expect(rebuilt!.shell.parentElement).toBe(newRoot)
|
||||
expect(rebuilt!.shell.isConnected).toBe(true)
|
||||
expect(stale.shell.isConnected).toBe(false)
|
||||
expect(getBrowserPageViewportContainer('page-1')).toBe(rebuilt!.container)
|
||||
})
|
||||
})
|
||||
|
||||
describe('syncBrowserPageChromeInset', () => {
|
||||
|
||||
@@ -65,11 +65,18 @@ export function ensureBrowserPageViewport(
|
||||
browserPageId: string,
|
||||
workspaceTabId: string
|
||||
): BrowserPageViewport | null {
|
||||
const root = slotViewportRoots.get(workspaceTabId)
|
||||
const existing = browserPageViewports.get(browserPageId)
|
||||
if (existing) {
|
||||
return existing
|
||||
if (!root || existing.shell.parentElement === root) {
|
||||
return existing
|
||||
}
|
||||
// Why: a remounted slot root (overlay unmount while hidden, cross-worktree tab move) strands
|
||||
// the cached shell in a removed subtree where its guest is already dead; drop it so the caller
|
||||
// rebuilds against the live root instead of rendering into detached DOM forever (STA-3228).
|
||||
existing.shell.remove()
|
||||
browserPageViewports.delete(browserPageId)
|
||||
}
|
||||
const root = slotViewportRoots.get(workspaceTabId)
|
||||
if (!root) {
|
||||
return null
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user