Skip to content

Fix browser portal leaking to other tabs on Bonsplit tab switch - #2000

Merged
lawrencecchen merged 3 commits into
manaflow-ai:mainfrom
anthhub:fix/browser-portal-leak-on-tab-switch
Mar 25, 2026
Merged

lawrencecchen merged 3 commits into
manaflow-ai:mainfrom
anthhub:fix/browser-portal-leak-on-tab-switch

Conversation

@anthhub

@anthhub anthhub commented Mar 23, 2026 •

Copy link
Copy Markdown

Summary

  • When switching between Bonsplit tabs within a workspace, portal-hosted WKWebViews from deselected browser panels could remain visible above the newly selected tab
  • Root cause: Bonsplit's keepAllAlive mode hides non-selected tabs via SwiftUI .opacity(0), but portal-hosted WKWebViews render at the AppKit window level and are not affected by SwiftUI opacity changes
  • Fix: explicitly hide browser portals for deselected tabs in the pane during applyTabSelectionNow, ensuring portal visibility is always in sync regardless of SwiftUI re-render timing

Test plan

  • Open a workspace with multiple tabs, including at least one browser tab
  • Switch from the browser tab to a terminal tab — the browser content should disappear immediately
  • Switch back to the browser tab — it should reappear correctly
  • Test with split panes: browser in one pane should not be affected when switching tabs in another pane
  • Test rapid tab switching to verify no flicker or stale portal frames

🤖 Generated with Claude Code


Summary by cubic

Stops browser portal layers from showing over other tabs when switching within a Bonsplit pane by explicitly hiding portals for deselected tabs. Keeps portal-hosted web views in sync with tab selection even when keepAllAlive uses SwiftUI opacity.

  • Bug Fixes
    • Hide browser portals for non-selected tabs in a pane during tab selection.
    • Resolves SwiftUI .opacity(0) vs AppKit window-level rendering mismatch.
    • Scopes the update to the active pane; other panes are unaffected.

Written for commit 91440f0. Summary will update on new commits.

Summary by CodeRabbit

  • Bug Fixes
    • Improved tab switching behavior to ensure browser content is properly hidden when switching between tabs, providing a more consistent experience when navigating multiple browser tabs.

austinywang and others added 3 commits March 23, 2026 04:48
XCUIApplication.launch() blocks ~60s then fails on headless WarpBuild
runners because foreground activation requires a GUI login session.

Apply the same pre-launch strategy used for the display resolution test:
- CI shell launches the app with env vars before running xcodebuild
- Test detects pre-launched app via manifest, uses activate() instead of
  launch() to avoid killing and relaunching the app
- Falls back to clicking the window for focus via accessibility framework

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
When switching between Bonsplit tabs within a workspace, portal-hosted
WKWebViews from deselected browser panels could remain visible above the
newly selected tab. This happened because Bonsplit's keepAllAlive mode
hides non-selected tabs via SwiftUI .opacity(0), but the portal layer
renders at the AppKit window level and is not affected by SwiftUI
opacity changes.

Explicitly hide browser portals for deselected tabs in the pane during
tab selection, ensuring the portal visibility state is always in sync
regardless of SwiftUI re-render timing.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@vercel

vercel Bot commented Mar 23, 2026

Copy link
Copy Markdown

@anthhub is attempting to deploy a commit to the Manaflow Team on Vercel.

A member of the Team first needs to authorize it.

@coderabbitai

coderabbitai Bot commented Mar 23, 2026 •

Copy link
Copy Markdown

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 74303213-b0a6-43b2-9226-8fdeb087787a

📥 Commits

Reviewing files that changed from the base of the PR and between 2aeb101 and 91440f0.

📒 Files selected for processing (1)
  • Sources/Workspace.swift

📝 Walkthrough

Walkthrough

Added a private helper method hideBrowserPortalsForDeselectedTabs(inPane:selectedTabId:) to Workspace.swift that iterates through tabs in a pane, resolves each deselected tab's BrowserPanel, and calls hideBrowserPortalView(source: "tabDeselected") to hide portal content. The helper is invoked during tab selection in applyTabSelectionNow(...) to ensure portal-hosted WKWebViews are hidden when tabs are deselected.

Changes

Cohort / File(s) Summary
Tab Selection Portal Management
Sources/Workspace.swift
Added private helper method hideBrowserPortalsForDeselectedTabs(inPane:selectedTabId:) that hides browser portal views for deselected tabs. Integrated into applyTabSelectionNow(...) tab-selection side-effect flow to explicitly hide portal-hosted WKWebViews unaffected by SwiftUI opacity changes in keepAllAlive mode.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~15 minutes

Possibly related PRs

Poem

🐰 When tabs are swapped, the portals hide away,
Like web views tucked in cozy stacks of hay,
No ghostly overlays linger in the light,
The workspace breathes, alive and bright! ✨

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 66.67% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately describes the main fix: explicitly hiding browser portals when switching tabs in Bonsplit to prevent visual leaking.
Description check ✅ Passed The description covers Summary and Test plan sections from the template, but lacks Testing methodology, Demo Video, Review Trigger commands, and Checklist items.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@greptile-apps

greptile-apps Bot commented Mar 23, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR fixes a visual leak where portal-hosted WKWebViews from deselected browser panels remained visible above the newly selected tab when switching Bonsplit tabs in keepAllAlive mode. The root cause is that SwiftUI .opacity(0) hides elements at the SwiftUI layer but has no effect on AppKit-level portal views, so the browser portal of the deselected tab was never explicitly hidden.

The fix adds a small private helper hideBrowserPortalsForDeselectedTabs(inPane:selectedTabId:) that iterates tabs in the active pane, skips the newly-selected tab, and calls hideBrowserPortalView(source: "tabDeselected") on any BrowserPanels found among the remaining tabs. This helper is called at the right point in applyTabSelectionNow — after focusedPane/selectedTabId are resolved and the "unfocus all other panels" loop — ensuring portal visibility is synchronised with the tab selection state.

Key design properties of the fix:

  • Correctly scoped to the focused pane: only tabs in the pane where the selection changed are affected; selected browser tabs in other split panes are untouched.
  • Nil-safe: both panelIdFromSurfaceId and the as? BrowserPanel cast are guarded, so non-browser and uninitialized tabs are silently skipped.
  • Re-show is handled by the existing SwiftUI path: once the pane's opacity flips back to 1, updateNSView sets desiredPortalVisibleInUI = true and re-shows the portal through BrowserWindowPortalRegistry.bind.
  • No automated test added: given the test quality policy (no source-inspection or stub-based tests), a meaningful regression test would need a full E2E harness with live WKWebViews; the PR provides a manual test plan instead.

Confidence Score: 5/5

  • The fix is minimal, well-scoped, and uses an established hide primitive already in use elsewhere in the codebase.
  • Single-file change adding ~13 lines of straightforward logic. The helper is correctly scoped to the focused pane (doesn't disturb other panes' browser portals), all lookups are nil-guarded, the hide primitive (hideBrowserPortalView) is already exercised in the workspaceRetire path, and re-showing when switching back relies on the existing SwiftUI bind path. No logic errors, no missing edge-case handling, no regressions expected.
  • No files require special attention.

Important Files Changed

Filename Overview
Sources/Workspace.swift Adds a targeted fix to explicitly hide browser portals for deselected tabs when applyTabSelectionNow runs; scoped correctly to the focused pane, guarded by nil-checks, and paired with an existing hide primitive. No issues found.

Sequence Diagram

sequenceDiagram
    participant User
    participant Workspace
    participant BonsplitController
    participant BrowserPanel
    participant BrowserWindowPortalRegistry

    User->>Workspace: Switch tab (e.g. browser → terminal)
    Workspace->>BonsplitController: focusPane(pane) / selectTab(tabId)
    BonsplitController-->>Workspace: focusedPane, selectedTabId resolved

    Note over Workspace: Unfocus all other panels

    Workspace->>Workspace: hideBrowserPortalsForDeselectedTabs(inPane: focusedPane, selectedTabId:)
    loop for each non-selected tab in pane
        Workspace->>BrowserPanel: hideBrowserPortalView(source: "tabDeselected")
        BrowserPanel->>BrowserWindowPortalRegistry: hide(webView:, source:)
        BrowserWindowPortalRegistry->>BrowserWindowPortalRegistry: entry.visibleInUI = false
        BrowserWindowPortalRegistry->>BrowserWindowPortalRegistry: containerView.isHidden = true
    end

    Workspace->>Workspace: activatePanel(panel, …)

    Note over User: Switch back to browser tab
    User->>Workspace: Switch tab (terminal → browser)
    Workspace->>BonsplitController: selectTab(browserTabId)
    BonsplitController-->>Workspace: opacity(1) triggers SwiftUI re-render
    Workspace->>BrowserWindowPortalRegistry: bind(webView:, visibleInUI: true, …)
    BrowserWindowPortalRegistry->>BrowserWindowPortalRegistry: containerView.isHidden = false
Loading

Reviews (1): Last reviewed commit: "Fix browser portal leaking to other tabs..." | Re-trigger Greptile

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No issues found across 1 file

@lawrencecchen
lawrencecchen merged commit 9a24db9 into manaflow-ai:main Mar 25, 2026
4 of 5 checks passed
@lawrencecchen

Copy link
Copy Markdown
Contributor

Thank you for the contribution!

bn-l pushed a commit to bn-l/cmux that referenced this pull request Apr 3, 2026
…flow-ai#2000)

* Pre-launch app for browser UI test on headless CI runners

XCUIApplication.launch() blocks ~60s then fails on headless WarpBuild
runners because foreground activation requires a GUI login session.

Apply the same pre-launch strategy used for the display resolution test:
- CI shell launches the app with env vars before running xcodebuild
- Test detects pre-launched app via manifest, uses activate() instead of
  launch() to avoid killing and relaunching the app
- Falls back to clicking the window for focus via accessibility framework

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* Revert "Pre-launch app for browser UI test on headless CI runners"

This reverts commit a540e2f.

* Fix browser portal leaking to other tabs on Bonsplit tab switch

When switching between Bonsplit tabs within a workspace, portal-hosted
WKWebViews from deselected browser panels could remain visible above the
newly selected tab. This happened because Bonsplit's keepAllAlive mode
hides non-selected tabs via SwiftUI .opacity(0), but the portal layer
renders at the AppKit window level and is not affected by SwiftUI
opacity changes.

Explicitly hide browser portals for deselected tabs in the pane during
tab selection, ensuring the portal visibility state is always in sync
regardless of SwiftUI re-render timing.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: austinpower1258 <austinwang115@gmail.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants