Skip to content

Fix translucent split wrapper layers - #29

Merged
lawrencecchen merged 5 commits into
mainfrom
task-split-transparency-disappears
Mar 17, 2026
Merged

lawrencecchen merged 5 commits into
mainfrom
task-split-transparency-disappears

Conversation

@lawrencecchen

@lawrencecchen lawrencecchen commented Mar 17, 2026 •

Copy link
Copy Markdown

Summary

  • add a runtime regression test for translucent split wrapper layers
  • keep split-only wrapper containers clear so translucent pane chrome is not composited twice

Testing

  • not run locally (repo policy)

Issues

  • Related: plain-text task "transparency disappears when i make a split (ie cmd+d)"

Summary by cubic

Fix translucent themes turning opaque after splitting by keeping split root and wrapper layers clear and moving background painting to the root. Adds a callback for user‑initiated tab closes.

  • Bug Fixes

    • Keep split root and wrapper containers clear and non-opaque; move pane background to SplitViewContainer to avoid alpha stacking and preserve translucency.
    • Maintain divider tint and add tests ensuring wrappers stay clear and split rendered alpha matches single‑pane.
    • Addresses Linear: transparency disappears when making a split (cmd+d).
  • New Features

    • Add onTabCloseRequest to BonsplitController to signal explicit tab-close gestures.
    • TabBarView calls this hook before closing, so hosts can handle last-surface/workspace logic.

Written for commit 02fa188. Summary will update on new commits.

Summary by CodeRabbit

  • New Features

    • Added a callback hook allowing host apps to respond when users request closing a tab.
  • Bug Fixes / Visual

    • Improved split and pane background handling so container surfaces render translucent/clear correctly.
  • Tests

    • Added tests validating split container translucency and rendered alpha remain correct after pane operations.

austinywang and others added 3 commits March 13, 2026 12:43
Allows the host application to distinguish user-initiated tab closes
(clicking the X button) from internal/programmatic closes, so it can
decide whether closing the last surface should also close the workspace.

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

@greptile-apps greptile-apps 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.

Your free trial has ended. If you'd like to continue receiving code reviews, you can add a payment method here.

@coderabbitai

coderabbitai Bot commented Mar 17, 2026 •

Copy link
Copy Markdown
📝 Walkthrough

Walkthrough

This PR adds a public optional hook onTabCloseRequest called before a tab is closed, updates TabBarView to invoke that hook during tab-close transactions, and makes multiple split/pane container views non-opaque and use clear backgrounds to ensure translucent rendering. Tests verifying translucency were added (with duplicated test code present).

Changes

Cohort / File(s) Summary
Public API
Sources/Bonsplit/Public/BonsplitController.swift
Added public optional property onTabCloseRequest for host apps to observe user-initiated tab-close requests.
Tab bar wiring
Sources/Bonsplit/Internal/Views/TabBarView.swift
Calls controller.onTabCloseRequest?(TabID(id: tab.id), pane.id) inside the tab-close transaction before performing the actual close.
Split / Pane visual changes
Sources/Bonsplit/Internal/Views/SplitContainerView.swift, Sources/Bonsplit/Internal/Views/SplitNodeView.swift, Sources/Bonsplit/Internal/Views/PaneContainerView.swift, Sources/Bonsplit/Internal/Views/SplitViewContainer.swift
Removed chromeBackgroundColor; set layers' backgroundColor to NSColor.clear.cgColor and isOpaque = false in multiple wrapper/container views; removed reliance on appearance for PaneContainerView background; added a background modifier to SplitViewContainer.
Tests
Tests/BonsplitTests/BonsplitTests.swift
Added tests and helpers to assert split container translucency and rendered alpha; the new tests/helpers appear duplicated within the diff.

Sequence Diagram(s)

sequenceDiagram
    participant User as User
    participant TabBar as TabBarView
    participant Controller as BonsplitController

    User->>TabBar: Clicks tab close button
    TabBar->>TabBar: Begin close transaction
    TabBar->>Controller: onTabCloseRequest?(TabID, PaneID)
    Note right of Controller: Optional host callback invoked
    TabBar->>Controller: closeTab(...)  %% proceed to close tab
    TabBar->>TabBar: Commit transaction / update UI
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

🐇 I nudge a tab to say goodbye,
A whisper sent before it flies,
My panes shed paint to show the sky,
Clear glass where gentle shadows lie,
Tests hop in to prove my prize.

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 12.50% 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
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Fix translucent split wrapper layers' directly aligns with the main objective of the PR: fixing transparency issues in split containers by keeping wrapper layers clear to prevent double compositing of translucent chrome.

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

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch task-split-transparency-disappears
📝 Coding Plan
  • Generate coding plan for human review comments

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.

@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 4 files

@coderabbitai coderabbitai 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.

Actionable comments posted: 1

🧹 Nitpick comments (4)
Sources/Bonsplit/Internal/Views/SplitNodeView.swift (1)

75-78: Optional: extract repeated layer configuration into a helper.

The same layer setup appears in two places; a small helper would reduce drift risk in future tweaks.

♻️ Suggested refactor
+    private func configureTransparentLayer(for view: NSView) {
+        view.wantsLayer = true
+        view.layer?.backgroundColor = NSColor.clear.cgColor
+        view.layer?.isOpaque = false
+        view.layer?.masksToBounds = true
+    }
+
     func makeNSView(context: Context) -> NSView {
@@
-        containerView.wantsLayer = true
-        containerView.layer?.backgroundColor = NSColor.clear.cgColor
-        containerView.layer?.isOpaque = false
-        containerView.layer?.masksToBounds = true
+        configureTransparentLayer(for: containerView)
@@
     func updateNSView(_ nsView: NSView, context: Context) {
@@
-        nsView.wantsLayer = true
-        nsView.layer?.backgroundColor = NSColor.clear.cgColor
-        nsView.layer?.isOpaque = false
-        nsView.layer?.masksToBounds = true
+        configureTransparentLayer(for: nsView)

Also applies to: 98-101

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Sources/Bonsplit/Internal/Views/SplitNodeView.swift` around lines 75 - 78,
The repeated container layer setup (containerView.wantsLayer = true;
containerView.layer?.backgroundColor = NSColor.clear.cgColor;
containerView.layer?.isOpaque = false; containerView.layer?.masksToBounds =
true) in SplitNodeView should be extracted into a single private helper (e.g.
configureContainerLayer(_:)/setupContainerLayer()) and called from both
locations (the two blocks around containerView). Create the helper as a private
method on SplitNodeView that takes the view (or uses the instance property),
moves the four layer lines into it, and replace both duplicated blocks with
calls to that helper to ensure consistent configuration and easier future
changes.
Sources/Bonsplit/Internal/Views/SplitContainerView.swift (2)

327-332: Redundant layer configuration on existing containers.

These containers (arranged[0] and arranged[1]) are the same NSView instances created in makeNSView. Their layer properties are already configured and don't need to be re-set on every update cycle unless you expect external modification.

This is low priority since the overhead is minimal and the pattern matches the split view handling above.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Sources/Bonsplit/Internal/Views/SplitContainerView.swift` around lines 327 -
332, The repeated layer setup for firstContainer and secondContainer in the
update path is redundant because these are the same NSView instances
created/configured in makeNSView (arranged[0]/arranged[1]); remove the duplicate
calls (firstContainer.wantsLayer, layer?.backgroundColor, layer?.isOpaque and
corresponding secondContainer lines) from the update routine and ensure the
initial configuration remains only in makeNSView (or alternatively guard with an
if layer == nil check) so the views’ layer properties aren’t reset on every
update.

308-310: Consider consolidating layer configuration.

These layer properties are already set in makeNSView (lines 115-117). Re-applying them on every updateNSView call is defensive but adds redundant work. If the layer configuration is expected to be stable, you could remove this duplication.

That said, if there's a concern about layer state being externally modified, keeping it is a valid defensive choice.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Sources/Bonsplit/Internal/Views/SplitContainerView.swift` around lines 308 -
310, The split view's layer properties are being set twice; remove the redundant
reconfiguration from updateNSView by deleting the splitView.wantsLayer /
splitView.layer?.backgroundColor / splitView.layer?.isOpaque lines so the layer
is only configured in makeNSView (where it's already set), or if you
intentionally want defensive reapplication, keep them but add a short comment in
updateNSView explaining why the repetition is necessary; refer to
SplitContainerView, makeNSView, updateNSView and the splitView variable to
locate the changes.
Tests/BonsplitTests/BonsplitTests.swift (1)

917-920: Use bounded polling instead of fixed sleep to reduce UI test flakiness.

The fixed 50ms delay can be timing-sensitive on slower CI machines. Polling until a renderable pixel is available (with timeout) is more reliable.

Proposed refactor
-        window.makeKeyAndOrderFront(nil)
-        contentView.layoutSubtreeIfNeeded()
-        RunLoop.current.run(until: Date().addingTimeInterval(0.05))
-        contentView.layoutSubtreeIfNeeded()
+        window.makeKeyAndOrderFront(nil)
+        let deadline = Date().addingTimeInterval(0.5)
+        while Date() < deadline {
+            contentView.layoutSubtreeIfNeeded()
+            if renderedColor(in: hostingView, at: samplePoint) != nil {
+                break
+            }
+            RunLoop.current.run(mode: .default, before: Date().addingTimeInterval(0.01))
+        }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Tests/BonsplitTests/BonsplitTests.swift` around lines 917 - 920, Replace the
fixed 50ms sleep (RunLoop.current.run(until: Date().addingTimeInterval(0.05)))
with bounded polling: after calling window.makeKeyAndOrderFront(nil) and
contentView.layoutSubtreeIfNeeded(), loop running the runloop in short
increments and repeatedly check a render-ready condition on contentView (for
example: a non-nil layer/presentation, a valid
bitmapImageRepForCachingDisplay(_:), or any pixel-ready probe) until the
condition is true or a timeout elapses; ensure the loop breaks on success and
fails the test on timeout so window.makeKeyAndOrderFront,
contentView.layoutSubtreeIfNeeded and the previous RunLoop usage are the only
touched symbols.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@Sources/Bonsplit/Internal/Views/SplitViewContainer.swift`:
- Line 20: Replace the unconditional
.background(TabBarColors.paneBackground(for: appearance)) so the split wrapper
remains clear for translucent chrome: introduce/use a splitWrapperBackground
policy that inspects the chrome/pane color returned by
TabBarColors.paneBackground(for: appearance) (or appearance.chrome background)
and returns Color.clear unless that color's alpha == 1.0, and then call
.background(splitWrapperBackground(for: appearance or chromeColor)) at the
SplitViewContainer call site; keep SplitContainerView and SplitNodeView behavior
unchanged and ensure the new helper only uses opaque colors when alpha is
exactly 1.0.

---

Nitpick comments:
In `@Sources/Bonsplit/Internal/Views/SplitContainerView.swift`:
- Around line 327-332: The repeated layer setup for firstContainer and
secondContainer in the update path is redundant because these are the same
NSView instances created/configured in makeNSView (arranged[0]/arranged[1]);
remove the duplicate calls (firstContainer.wantsLayer, layer?.backgroundColor,
layer?.isOpaque and corresponding secondContainer lines) from the update routine
and ensure the initial configuration remains only in makeNSView (or
alternatively guard with an if layer == nil check) so the views’ layer
properties aren’t reset on every update.
- Around line 308-310: The split view's layer properties are being set twice;
remove the redundant reconfiguration from updateNSView by deleting the
splitView.wantsLayer / splitView.layer?.backgroundColor /
splitView.layer?.isOpaque lines so the layer is only configured in makeNSView
(where it's already set), or if you intentionally want defensive reapplication,
keep them but add a short comment in updateNSView explaining why the repetition
is necessary; refer to SplitContainerView, makeNSView, updateNSView and the
splitView variable to locate the changes.

In `@Sources/Bonsplit/Internal/Views/SplitNodeView.swift`:
- Around line 75-78: The repeated container layer setup
(containerView.wantsLayer = true; containerView.layer?.backgroundColor =
NSColor.clear.cgColor; containerView.layer?.isOpaque = false;
containerView.layer?.masksToBounds = true) in SplitNodeView should be extracted
into a single private helper (e.g.
configureContainerLayer(_:)/setupContainerLayer()) and called from both
locations (the two blocks around containerView). Create the helper as a private
method on SplitNodeView that takes the view (or uses the instance property),
moves the four layer lines into it, and replace both duplicated blocks with
calls to that helper to ensure consistent configuration and easier future
changes.

In `@Tests/BonsplitTests/BonsplitTests.swift`:
- Around line 917-920: Replace the fixed 50ms sleep (RunLoop.current.run(until:
Date().addingTimeInterval(0.05))) with bounded polling: after calling
window.makeKeyAndOrderFront(nil) and contentView.layoutSubtreeIfNeeded(), loop
running the runloop in short increments and repeatedly check a render-ready
condition on contentView (for example: a non-nil layer/presentation, a valid
bitmapImageRepForCachingDisplay(_:), or any pixel-ready probe) until the
condition is true or a timeout elapses; ensure the loop breaks on success and
fails the test on timeout so window.makeKeyAndOrderFront,
contentView.layoutSubtreeIfNeeded and the previous RunLoop usage are the only
touched symbols.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 5f871c2f-3b0a-46ca-b9c8-71fe637c4f0d

📥 Commits

Reviewing files that changed from the base of the PR and between 44ec863 and 02fa188.

📒 Files selected for processing (5)
  • Sources/Bonsplit/Internal/Views/PaneContainerView.swift
  • Sources/Bonsplit/Internal/Views/SplitContainerView.swift
  • Sources/Bonsplit/Internal/Views/SplitNodeView.swift
  • Sources/Bonsplit/Internal/Views/SplitViewContainer.swift
  • Tests/BonsplitTests/BonsplitTests.swift
💤 Files with no reviewable changes (1)
  • Sources/Bonsplit/Internal/Views/PaneContainerView.swift

Comment thread Sources/Bonsplit/Internal/Views/SplitViewContainer.swift
@lawrencecchen
lawrencecchen merged commit d696f82 into main Mar 17, 2026
2 of 3 checks passed
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.

2 participants