Skip to content

Separate pane background from tab chrome - #105

Merged
lawrencecchen merged 27 commits into
mainfrom
feat-pane-background-color
Apr 27, 2026
Merged

lawrencecchen merged 27 commits into
mainfrom
feat-pane-background-color

Conversation

@lawrencecchen

@lawrencecchen lawrencecchen commented Apr 26, 2026 •

Copy link
Copy Markdown

Adds an optional pane background color so embedders can keep tab chrome themed without repainting terminal panes underneath their own renderer.


Summary by cubic

Separates the pane background from the tab chrome and adds fully configurable split action buttons with host‑defined actions. Softens the split‑button backdrop with finer fade control and new content occlusion to keep contrast without heavy visuals.

  • New Features

    • New appearance.chromeColors.paneBackgroundHex; falls back to backgroundHex when unset.
    • New appearance.chromeColors.tabBarBackgroundHex and splitButtonBackdropHex for finer surface control.
    • Configurable split buttons via appearance.splitButtons; duplicate ids are ignored. Icons support SF Symbols, emoji (with scale), or image data; auto‑detects template SVGs and caches images.
    • New APIs for host actions: BonsplitController.requestCustomAction(_:inPane:) and BonsplitDelegate.splitTabBar(_:didRequestCustomAction:inPane:).
    • New appearance.splitButtonBackdropEffect (and splitButtonBackdropStyle) to set fade width, content fade width, solid width, ramp start, leading/trailing opacity, content occlusion fraction, and whether tab content is masked. Backdrops paint when any split‑backdrop, tab‑bar, or chrome background is customized; defaults are softened for a subtler fade.
    • Trailing inset now matches the configured button count; minimal mode doesn’t reserve space.
    • Double‑clicking empty trailing space triggers the first newTerminal button if present; otherwise no‑op.
    • Host‑controlled pane tab shortcuts via BonsplitController.tabShortcutHintsEnabled.
  • Bug Fixes

    • Added a precomposited, opaque backdrop option matching the pane background to avoid double‑compositing artifacts.

Written for commit 3ac045a. Summary will update on new commits.

Summary by CodeRabbit

  • New Features

    • Fully configurable tab-bar split buttons (reorderable, system image/emoji/image-data icons, per-button tooltips) and public APIs to request/handle host-defined custom actions.
    • New appearance properties and public color accessors for tab bar, pane background, and split-button backdrop; split-button backdrop style/effect options.
  • Improvements

    • Backdrop compositing with translucency and focus-aware alpha; pane/tab color resolution refined.
    • Dynamic trailing inset based on button count; improved hover, press animation, masking, and double-click behavior.
  • Tests

    • Expanded coverage for icons, image caching, hit-testing, double-clicks, spacing, and custom actions.

@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.
To continue using code reviews, add credits to your account and enable them for code reviews in your settings.

@coderabbitai

coderabbitai Bot commented Apr 26, 2026 •

Copy link
Copy Markdown

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Replaces hardcoded split-button behavior with configurable split-action buttons and backdrop styles; adds pane-specific color resolution and NSColor accessors for tab bar/chrome/backdrop; forwards custom button actions via BonsplitController/BonsplitDelegate; refactors TabBar trailing-reserve sizing, rendering, and tests.

Changes

Cohort / File(s) Summary
Color & Styling
Sources/Bonsplit/Internal/Styling/TabBarColors.swift
Adds pane-specific color resolution and new NSColor accessors: nsColorBarBackground, nsColorChromeBackground, nsColorSplitButtonBackdropSurface, nsColorSplitButtonBackdrop, and shouldPaintSplitButtonBackdrop. Tab/pane color selection now prefers appearance.chromeColors.paneBackgroundHex/tabBarBackgroundHex/splitButtonBackdropHex with chained fallbacks.
Tab Bar UI
Sources/Bonsplit/Internal/Views/TabBarView.swift
Reworks split-button layout to use appearance.splitButtons (per-button icons/tooltips/actions), makes trailing reserved inset button-count aware, centralizes new-split activation, adds onDoubleClick path, implements per-button imageData caching and hover/press visuals, and switches to SplitButtonBackdropStyle/effect-driven backdrop masking and blending.
Public Models & Config
Sources/Bonsplit/Public/BonsplitConfiguration.swift
Adds SplitActionButton model (Icon & Action enums, Codable flexibility, defaults), SplitButtonBackdropStyle and SplitButtonBackdropEffect, extends Appearance.ChromeColors with tabBarBackgroundHex, splitButtonBackdropHex, paneBackgroundHex, and adds splitButtons, splitButtonBackdropStyle, and splitButtonBackdropEffect to Appearance (dedupes by id).
Public Controller / Delegate
Sources/Bonsplit/Public/BonsplitController.swift, Sources/Bonsplit/Public/BonsplitDelegate.swift
Adds BonsplitController.requestCustomAction(_:inPane:) and delegate callback splitTabBar(... didRequestCustomAction:inPane:) with a default empty implementation to surface host-defined custom actions.
Tests
Tests/BonsplitTests/BonsplitTests.swift
Adds tests and spy for custom actions, validates SplitActionButton JSON round-trips (including legacy mapping), expands icon coverage (emoji/imageData/SVG template detection and caching), updates trailing-inset assertions to depend on buttonCount, and revises double-click tests to use hit-testing helpers and delegate expectations.

Sequence Diagram

sequenceDiagram
    participant User
    participant TabBarView
    participant BonsplitController
    participant BonsplitDelegate
    participant Host

    User->>TabBarView: Click split action button
    activate TabBarView

    alt action is .custom(identifier)
        TabBarView->>BonsplitController: requestCustomAction(identifier, pane)
        activate BonsplitController
        BonsplitController->>BonsplitDelegate: splitTabBar(... didRequestCustomAction: identifier, inPane: pane)
        activate BonsplitDelegate
        BonsplitDelegate->>Host: forward custom action
        Host-->>BonsplitDelegate: handled
        deactivate BonsplitDelegate
        deactivate BonsplitController
    else predefined action (newTerminal/newBrowser/splitRight/splitDown)
        TabBarView->>TabBarView: performSplitActionButton(action, pane)
    end

    deactivate TabBarView
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Possibly related PRs

Poem

🐇
I nibble on hex and blend a soft hue,
Buttons that hop — emoji, image, or clue,
A custom hop sent through controller and hand,
Pane, chrome, and backdrop resolve where they land,
Cached sprites and quiet clicks — my tab-bar band.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 3.41% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The pull request title 'Separate pane background from tab chrome' clearly summarizes the main architectural change, which is the most prominent aspect of the changeset.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

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

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat-pane-background-color

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 Apr 26, 2026 •

Copy link
Copy Markdown

Greptile Summary

This PR does two things: it adds paneBackgroundHex to ChromeColors so embedders can paint terminal panes independently of tab-chrome theming, and it replaces the hard-coded split-button set with a configurable [SplitActionButton] array that supports system images, emoji, and custom image data — including a new custom action type dispatched through the delegate. The double-click-to-new-terminal shortcut and button layout math are updated throughout to respect the dynamic button list.

Confidence Score: 4/5

Safe to merge; all findings are P2 style/quality concerns with no functional regressions on the changed paths.

No P0 or P1 issues found. The two cache cost-accounting gaps (invalid-image key memory and compressed-vs-uncompressed image size) can cause the cache to use more memory than intended under adversarial or heavy image loads, but neither causes data loss or incorrect behavior. The redundant guard and rawValue ambiguity are cosmetic. Comprehensive test coverage increases confidence.

Sources/Bonsplit/Internal/Views/TabBarView.swift — SplitActionButtonImageCache cost accounting for both the valid and invalid image caches.

Important Files Changed

Filename Overview
Sources/Bonsplit/Internal/Styling/TabBarColors.swift Adds paneBackgroundColor helper that resolves paneBackgroundHex before falling back to chromeBackgroundColor; updates both public pane-background accessors to use it. Clean and correct.
Sources/Bonsplit/Internal/Views/TabBarView.swift Large refactor: split buttons become data-driven via SplitActionButton; adds SplitActionButtonImageCache (cost accounting issues for both the valid-image and invalid-image caches); adds onDoubleClick wiring through TabBarDragAndHoverView; adds window key/resign notification observers for hover-state sync.
Sources/Bonsplit/Public/BonsplitConfiguration.swift Introduces SplitActionButton with Icon and Action enums (full Codable/Hashable), adds paneBackgroundHex to ChromeColors, and splitButtons property with duplicate-ID deduplication. rawValue ambiguity for .custom mirroring built-in names is a minor concern.
Sources/Bonsplit/Public/BonsplitController.swift Adds requestCustomAction(_:inPane:) which forwards to the delegate; mirrors the existing requestNewTab pattern exactly.
Sources/Bonsplit/Public/BonsplitDelegate.swift Adds didRequestCustomAction required method plus a no-op default extension implementation, keeping backwards compatibility.
Tests/BonsplitTests/BonsplitTests.swift Comprehensive coverage for all new features: codec round-trips, image cache, SVG template detection, custom action delegate, duplicate-ID deduplication, pane-background independence, and double-click behavior changes.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[TabBarView\ndouble-click / button tap] --> B{action}
    B -->|newTerminal| C[requestNewTab\nkind: terminal]
    B -->|newBrowser| D[requestNewTab\nkind: browser]
    B -->|splitRight| E[splitPane\norientation: horizontal]
    B -->|splitDown| F[splitPane\norientation: vertical]
    B -->|custom identifier| G[requestCustomAction\nidentifier]
    G --> H[BonsplitDelegate\ndidRequestCustomAction]
    C --> I[BonsplitDelegate\ndidRequestNewTab]
    D --> I

    subgraph Appearance
        J[ChromeColors\nbackgroundHex] --> K[tab chrome color]
        L[ChromeColors\npaneBackgroundHex] -->|set| M[pane background color]
        L -->|unset| K
        N[splitButtons array] --> O[SplitActionButtonImageCache\nNSCache keyed by NSData]
        O --> P[rendered NSImage\ntemplate or original]
    end
Loading

Comments Outside Diff (3)

  1. Sources/Bonsplit/Internal/Views/TabBarView.swift, line 334-366 (link)

    P2 invalidImageData cache key memory not bounded by cost

    NSCache charges cost against the value, not the key. Each invalid-image entry stores the full NSData (potentially megabytes) as the key, but the cost is capped at 1 KB. With countLimit = 256, up to 256 × (full image size) bytes can accumulate in the key objects before any eviction kicks in, so the effective memory ceiling of this cache is far larger than the 512 KB totalCostLimit implies. Consider storing a truncated hash or a content-addressed identifier as the key instead of the raw bytes.

  2. Sources/Bonsplit/Internal/Views/TabBarView.swift, line 298-323 (link)

    P2 Redundant isInteractive guard

    performNewTerminalSplitButtonAction checks splitViewController.isInteractive and returns false if the check fails, but then calls performSplitActionButton(_:), which opens with the identical guard. The double check is harmless but could mislead a reader into thinking only the outer guard is authoritative. Consider removing the guard from performNewTerminalSplitButtonAction and trusting performSplitActionButton to enforce it, or removing it from performSplitActionButton for this call path.

    Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

  3. Sources/Bonsplit/Public/BonsplitConfiguration.swift, line 605-618 (link)

    P2 .custom shares rawValue with the built-in it mirrors

    Action.rawValue returns the associated identifier string for .custom, so .custom("newTerminal").rawValue == "newTerminal" == Action.newTerminal.rawValue. This is fine for the current encoding path (.custom uses a keyed container, not rawValue), but if any future code path switches on rawValue expecting unique values it will silently mismatch. A distinct prefix (e.g. "custom:\(identifier)") would make the contract clearer.

Reviews (1): Last reviewed commit: "Separate pane background from tab chrome" | Re-trigger Greptile

Comment on lines 353 to +364
@@ -322,7 +361,8 @@ struct TabBarView: View {
private var trailingTabContentInset: CGFloat {
TabBarStyling.trailingTabContentInset(
showSplitButtons: showSplitButtons,
isMinimalMode: isMinimalMode
isMinimalMode: isMinimalMode,

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Image cache cost undercounts rendered bitmap memory

The cost stored for a successfully decoded NSImage is max(1, data.count) — the compressed source bytes. For PNG or JPEG inputs, the decompressed in-memory representation is typically several times larger; for SVG, an NSImage backed by a NSPDFImageRep or Core Graphics rasterisation can be much larger still. When the cache compares total cost against totalCostLimit = 8 MB, it will think it is well under budget while the actual resident memory is much higher. Consider using image.representations.compactMap { $0 as? NSBitmapImageRep }.reduce(0) { $0 + $1.bytesPerRow * $1.pixelsHigh } as a more accurate cost, with data.count as a reasonable fallback.

@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: 2

🤖 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/Styling/TabBarColors.swift`:
- Around line 20-27: paneBackgroundColor(for:) currently returns nil when
appearance.chromeColors.paneBackgroundHex exists but is malformed, causing
callers to fall back all the way to .textBackgroundColor; update
paneBackgroundColor(for:) to try constructing NSColor(bonsplitHex: value) and if
that returns nil (malformed hex) fall back to chromeBackgroundColor(for:
appearance) instead of returning nil. Locate the paneBackgroundColor(for:)
function and change the guard to attempt creating the color, returning the
created NSColor when valid and chromeBackgroundColor(for:) when invalid or
missing.

In `@Sources/Bonsplit/Internal/Views/TabBarView.swift`:
- Around line 1091-1111: The cache cost currently uses compressed data.count
which underestimates memory; in image(for data: Data) compute the decoded bitmap
byte size and use that as the cache cost instead of data.count: after creating
the NSImage, iterate its representations (or obtain an NSBitmapImageRep/CGImage)
to calculate bytes = sum(representation.bytesPerRow * representation.pixelsHigh)
or pixelsWide * pixelsHigh * (bitsPerPixel/8) for each bitmap rep, clamp to at
least 1, and pass that value to images.setObject(..., cost: decodedByteCount);
also use the same decodedByteCount when marking invalidImageData so the cache
accounting reflects actual decoded memory usage.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: ab037dda-e91d-4e7c-8a0b-de703c99bdad

📥 Commits

Reviewing files that changed from the base of the PR and between c1018c5 and 6cb1b73.

📒 Files selected for processing (6)
  • Sources/Bonsplit/Internal/Styling/TabBarColors.swift
  • Sources/Bonsplit/Internal/Views/TabBarView.swift
  • Sources/Bonsplit/Public/BonsplitConfiguration.swift
  • Sources/Bonsplit/Public/BonsplitController.swift
  • Sources/Bonsplit/Public/BonsplitDelegate.swift
  • Tests/BonsplitTests/BonsplitTests.swift

Comment thread Sources/Bonsplit/Internal/Styling/TabBarColors.swift
Comment on lines +1091 to +1111
func image(for data: Data) -> NSImage? {
let key = data as NSData
if let image = images.object(forKey: key) {
return image
}
if invalidImageData.object(forKey: key) != nil {
return nil
}

guard let image = NSImage(data: data) else {
invalidImageData.setObject(
NSNumber(value: true),
forKey: key,
cost: max(1, min(data.count, 1024))
)
return nil
}
image.isTemplate = TabBarStyling.imageDataShouldRenderAsTemplate(data)

images.setObject(image, forKey: key, cost: max(1, data.count))
return image

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Use decoded image size for the cache cost.

The cache is capped, but the cost is based on data.count, which is only the compressed payload size. A small PNG can decode into a much larger bitmap, so the 8 MB limit will not actually bound memory for oversized split-button assets.

♻️ Proposed fix
     func image(for data: Data) -> NSImage? {
         let key = data as NSData
         if let image = images.object(forKey: key) {
             return image
         }
@@
         guard let image = NSImage(data: data) else {
             invalidImageData.setObject(
                 NSNumber(value: true),
                 forKey: key,
                 cost: max(1, min(data.count, 1024))
             )
             return nil
         }
         image.isTemplate = TabBarStyling.imageDataShouldRenderAsTemplate(data)
-
-        images.setObject(image, forKey: key, cost: max(1, data.count))
+        let decodedCost = image.representations
+            .compactMap { $0 as? NSBitmapImageRep }
+            .reduce(0) { $0 + ($1.bytesPerRow * $1.pixelsHigh) }
+
+        images.setObject(
+            image,
+            forKey: key,
+            cost: max(1, decodedCost > 0 ? decodedCost : data.count)
+        )
         return image
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Sources/Bonsplit/Internal/Views/TabBarView.swift` around lines 1091 - 1111,
The cache cost currently uses compressed data.count which underestimates memory;
in image(for data: Data) compute the decoded bitmap byte size and use that as
the cache cost instead of data.count: after creating the NSImage, iterate its
representations (or obtain an NSBitmapImageRep/CGImage) to calculate bytes =
sum(representation.bytesPerRow * representation.pixelsHigh) or pixelsWide *
pixelsHigh * (bitsPerPixel/8) for each bitmap rep, clamp to at least 1, and pass
that value to images.setObject(..., cost: decodedByteCount); also use the same
decodedByteCount when marking invalidImageData so the cache accounting reflects
actual decoded memory usage.

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

2 issues found across 6 files

You’re at about 99% of the monthly review limit. You may want to disable incremental reviews to conserve quota. Reviews will continue until that limit is exceeded. If you need help avoiding interruptions, please contact contact@cubic.dev.

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="Sources/Bonsplit/Internal/Styling/TabBarColors.swift">

<violation number="1" location="Sources/Bonsplit/Internal/Styling/TabBarColors.swift:26">
P2: Invalid `paneBackgroundHex` values skip fallback to `backgroundHex` and can produce an unexpected pane color.</violation>
</file>

<file name="Sources/Bonsplit/Internal/Views/TabBarView.swift">

<violation number="1" location="Sources/Bonsplit/Internal/Views/TabBarView.swift:933">
P2: Empty tab-bar new-tab interactions are incorrectly coupled to split-button visibility/config. If `.newTerminal` is not a visible split button, click/double-click in the empty tab-bar area stops creating a new tab.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

guard let value = appearance.chromeColors.paneBackgroundHex else {
return chromeBackgroundColor(for: appearance)
}
return NSColor(bonsplitHex: value)

@cubic-dev-ai cubic-dev-ai Bot Apr 26, 2026 •

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2: Invalid paneBackgroundHex values skip fallback to backgroundHex and can produce an unexpected pane color.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At Sources/Bonsplit/Internal/Styling/TabBarColors.swift, line 26:

<comment>Invalid `paneBackgroundHex` values skip fallback to `backgroundHex` and can produce an unexpected pane color.</comment>

<file context>
@@ -17,6 +17,15 @@ enum TabBarColors {
+        guard let value = appearance.chromeColors.paneBackgroundHex else {
+            return chromeBackgroundColor(for: appearance)
+        }
+        return NSColor(bonsplitHex: value)
+    }
+
</file context>
Suggested change
return NSColor(bonsplitHex: value)
return NSColor(bonsplitHex: value) ?? chromeBackgroundColor(for: appearance)
Fix with Cubic

Comment on lines +933 to +937
guard let button = visibleSplitButtons.first(where: { $0.action == .newTerminal }) else {
return false
}
performSplitActionButton(button)
return true

@cubic-dev-ai cubic-dev-ai Bot Apr 26, 2026 •

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2: Empty tab-bar new-tab interactions are incorrectly coupled to split-button visibility/config. If .newTerminal is not a visible split button, click/double-click in the empty tab-bar area stops creating a new tab.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At Sources/Bonsplit/Internal/Views/TabBarView.swift, line 933:

<comment>Empty tab-bar new-tab interactions are incorrectly coupled to split-button visibility/config. If `.newTerminal` is not a visible split button, click/double-click in the empty tab-bar area stops creating a new tab.</comment>

<file context>
@@ -789,47 +829,112 @@ struct TabBarView: View {
+
+    private func performNewTerminalSplitButtonAction() -> Bool {
+        guard splitViewController.isInteractive else { return false }
+        guard let button = visibleSplitButtons.first(where: { $0.action == .newTerminal }) else {
+            return false
+        }
</file context>
Suggested change
guard let button = visibleSplitButtons.first(where: { $0.action == .newTerminal }) else {
return false
}
performSplitActionButton(button)
return true
controller.requestNewTab(kind: "terminal", inPane: pane.id)
return true
Fix with Cubic

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

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

836-850: Prefer Identifiable over indices for the split-button ForEach.

SplitActionButton already conforms to Identifiable (stable id: String), so ForEach(buttons.indices, id: \.self) discards that identity and ties view state to position. If a host reorders or swaps appearance.splitButtons at runtime, SwiftUI will reuse views by index instead of by button id, which can cause incorrect transitions, stale tooltips, and animation glitches. Using the buttons directly preserves identity.

♻️ Proposed refactor
-        HStack(spacing: TabBarStyling.splitButtonsSpacing) {
-            ForEach(buttons.indices, id: \.self) { index in
-                let button = buttons[index]
-                Button {
-                    performSplitActionButton(button)
-                } label: {
-                    splitActionButtonIcon(button.icon)
-                }
-                .buttonStyle(SplitActionButtonStyle(appearance: appearance))
-                .safeHelp(splitActionButtonTooltip(button, tooltips: tooltips))
-            }
-        }
+        HStack(spacing: TabBarStyling.splitButtonsSpacing) {
+            ForEach(buttons) { button in
+                Button {
+                    performSplitActionButton(button)
+                } label: {
+                    splitActionButtonIcon(button.icon)
+                }
+                .buttonStyle(SplitActionButtonStyle(appearance: appearance))
+                .safeHelp(splitActionButtonTooltip(button, tooltips: tooltips))
+            }
+        }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Sources/Bonsplit/Internal/Views/TabBarView.swift` around lines 836 - 850,
Replace the index-based ForEach with identity-based iteration so SwiftUI tracks
each SplitActionButton by its id instead of position: iterate ForEach over
buttons (the appearance.splitButtons array) directly rather than
buttons.indices, relying on SplitActionButton's Identifiable conformance; keep
existing uses of performSplitActionButton(button),
splitActionButtonIcon(button.icon),
.buttonStyle(SplitActionButtonStyle(appearance: appearance)) and
.safeHelp(splitActionButtonTooltip(button, tooltips: tooltips)) so the behavior
and tooltips remain unchanged while preserving stable view identity.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@Sources/Bonsplit/Internal/Views/TabBarView.swift`:
- Around line 836-850: Replace the index-based ForEach with identity-based
iteration so SwiftUI tracks each SplitActionButton by its id instead of
position: iterate ForEach over buttons (the appearance.splitButtons array)
directly rather than buttons.indices, relying on SplitActionButton's
Identifiable conformance; keep existing uses of
performSplitActionButton(button), splitActionButtonIcon(button.icon),
.buttonStyle(SplitActionButtonStyle(appearance: appearance)) and
.safeHelp(splitActionButtonTooltip(button, tooltips: tooltips)) so the behavior
and tooltips remain unchanged while preserving stable view identity.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 796f97b4-26cb-4090-a1db-01776c700a17

📥 Commits

Reviewing files that changed from the base of the PR and between 6cb1b73 and 11c7313.

📒 Files selected for processing (3)
  • Sources/Bonsplit/Internal/Styling/TabBarColors.swift
  • Sources/Bonsplit/Internal/Views/TabBarView.swift
  • Tests/BonsplitTests/BonsplitTests.swift
🚧 Files skipped from review as they are similar to previous changes (2)
  • Sources/Bonsplit/Internal/Styling/TabBarColors.swift
  • Tests/BonsplitTests/BonsplitTests.swift

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

🧹 Nitpick comments (2)
Sources/Bonsplit/Internal/Views/TabBarView.swift (1)

186-194: String(decoding:as:) here is intentional — flag it for the linter.

SwiftLint's optional_data_string_conversion warning at line 187 is a false positive: imageDataShouldRenderAsTemplate deliberately tolerates invalid UTF-8 by relying on replacement substitution (covered by testCurrentColorSVGImageDataRendersAsTemplateWithInvalidUTF8Suffix). Consider an inline // swiftlint:disable:next optional_data_string_conversion so the lint signal stays clean.

♻️ Suggested annotation
     static func imageDataShouldRenderAsTemplate(_ data: Data) -> Bool {
+        // swiftlint:disable:next optional_data_string_conversion
+        // SVG inspection must tolerate invalid UTF-8 trailers; replacement chars are fine here.
         let text = String(decoding: data.prefix(4096), as: UTF8.self)
         let lowercased = text.lowercased()
         return lowercased.contains("<svg") && lowercased.contains("currentcolor")
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Sources/Bonsplit/Internal/Views/TabBarView.swift` around lines 186 - 194, Add
an inline SwiftLint disable for the false-positive
`optional_data_string_conversion` on the `imageDataShouldRenderAsTemplate(_:)`
function: specifically place a `// swiftlint:disable:next
optional_data_string_conversion` immediately above the `String(decoding:
data.prefix(4096), as: UTF8.self)` line so the linter ignores the intentional
tolerant UTF-8 decode; keep the rest of the implementation (the lowercased
checks for "<svg" and "currentcolor") and ensure existing test
`testCurrentColorSVGImageDataRendersAsTemplateWithInvalidUTF8Suffix` still
validates the behavior.
Sources/Bonsplit/Public/BonsplitConfiguration.swift (1)

106-175: Optional: validate emoji scale at construction too, for round-trip symmetry.

emojiScale(in:) rejects non-finite/non-positive scales on decode, but the public Icon.emoji(_, scale:) case constructor and the encode(to:) path don't. So Icon.emoji("x", scale: -1) is constructible and encodable, but the resulting JSON will fail to decode. Runtime rendering is safe because emojiIconFontSize already clamps, so this is just an API tidiness concern.

♻️ One way to enforce symmetry
             public func encode(to encoder: Encoder) throws {
                 var container = encoder.container(keyedBy: CodingKeys.self)
                 switch self {
                 case .systemImage(let name):
                     try container.encode("systemImage", forKey: .type)
                     try container.encode(name, forKey: .name)
                 case .emoji(let value, let scale):
                     try container.encode("emoji", forKey: .type)
                     try container.encode(value, forKey: .value)
-                    if scale != 1 {
+                    guard scale.isFinite, scale > 0 else {
+                        throw EncodingError.invalidValue(scale, .init(
+                            codingPath: container.codingPath + [CodingKeys.scale],
+                            debugDescription: "Emoji icon scale must be a positive finite number"
+                        ))
+                    }
+                    if scale != 1 {
                         try container.encode(scale, forKey: .scale)
                     }

Or — depending on whether you'd rather fail loud — sanitize at construction with a static factory.

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

In `@Sources/Bonsplit/Public/BonsplitConfiguration.swift` around lines 106 - 175,
The emoji scale is validated on decode via emojiScale(in:) but not when someone
constructs Icon.emoji(...) or when encoding, causing asymmetry; add the same
finite-and-positive check in encode(to:) before writing the scale (throw
DecodingError.dataCorruptedError or EncodingError.invalidValue as appropriate)
and provide a public factory like static func emoji(_ value: String, scale:
Double = 1) -> Icon that enforces/normalizes the scale (or documents use),
reusing emojiScale logic for consistency; update references to Icon.emoji
construction to prefer the factory for round-trip symmetry.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@Sources/Bonsplit/Internal/Views/TabBarView.swift`:
- Around line 186-194: Add an inline SwiftLint disable for the false-positive
`optional_data_string_conversion` on the `imageDataShouldRenderAsTemplate(_:)`
function: specifically place a `// swiftlint:disable:next
optional_data_string_conversion` immediately above the `String(decoding:
data.prefix(4096), as: UTF8.self)` line so the linter ignores the intentional
tolerant UTF-8 decode; keep the rest of the implementation (the lowercased
checks for "<svg" and "currentcolor") and ensure existing test
`testCurrentColorSVGImageDataRendersAsTemplateWithInvalidUTF8Suffix` still
validates the behavior.

In `@Sources/Bonsplit/Public/BonsplitConfiguration.swift`:
- Around line 106-175: The emoji scale is validated on decode via
emojiScale(in:) but not when someone constructs Icon.emoji(...) or when
encoding, causing asymmetry; add the same finite-and-positive check in
encode(to:) before writing the scale (throw DecodingError.dataCorruptedError or
EncodingError.invalidValue as appropriate) and provide a public factory like
static func emoji(_ value: String, scale: Double = 1) -> Icon that
enforces/normalizes the scale (or documents use), reusing emojiScale logic for
consistency; update references to Icon.emoji construction to prefer the factory
for round-trip symmetry.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: a4dd5c33-d58a-4de0-8706-aba8dfae12ca

📥 Commits

Reviewing files that changed from the base of the PR and between 11c7313 and b8a8e39.

📒 Files selected for processing (4)
  • Sources/Bonsplit/Internal/Styling/TabBarColors.swift
  • Sources/Bonsplit/Internal/Views/TabBarView.swift
  • Sources/Bonsplit/Public/BonsplitConfiguration.swift
  • Tests/BonsplitTests/BonsplitTests.swift
🚧 Files skipped from review as they are similar to previous changes (1)
  • Sources/Bonsplit/Internal/Styling/TabBarColors.swift

@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 (2)
Sources/Bonsplit/Public/BonsplitConfiguration.swift (2)

349-374: Consider conforming SplitButtonBackdropEffect to Equatable.

SplitButtonTooltips (and similar appearance value types) is Sendable, Equatable, but SplitButtonBackdropEffect is only Sendable. Adding Equatable (synthesizable since all stored properties are Equatable) lets hosts use it with SwiftUI onChange(of:), ==, and test assertions without writing custom comparators. Same applies to making the new Appearance properties uniformly comparable.

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

In `@Sources/Bonsplit/Public/BonsplitConfiguration.swift` around lines 349 - 374,
SplitButtonBackdropEffect is currently Sendable but not Equatable which prevents
using it with SwiftUI onChange/== and tests; add Equatable conformance to the
struct declaration (public struct SplitButtonBackdropEffect: Sendable,
Equatable) so the compiler can synthesize == (all stored properties are already
Equatable), and mirror this approach for related appearance value types (e.g.,
any new Appearance properties) so they are also Equatable and uniformly
comparable.

274-284: Consider validating Icon.emoji scale in the constructor too.

emojiScale(in:) enforces scale.isFinite && scale > 0 only on the decode path. A caller constructing SplitActionButton(id:..., icon: .emoji("🚀", scale: .nan), ...) (or 0/negative) bypasses this check, then emojiIconFontSize(scale:) in TabBarView falls back to 1, but the value still participates in Hashable/Equatable (NaN is pathological for Hashable) and round-trips asymmetrically through Codable (the decoder would reject what the encoder just wrote).

Either clamp/validate in SplitActionButton.init (or in a factory for .emoji), or document that .emoji(_, scale:) requires a positive finite value.

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

In `@Sources/Bonsplit/Public/BonsplitConfiguration.swift` around lines 274 - 284,
The SplitActionButton initializer currently accepts Icon.emoji values without
validating the emoji scale, which can allow non-finite or non-positive scales
(NaN/0/negative) to leak into Equatable/Hashable and into UI sizing; update
SplitActionButton.init (or add a small factory/validator used by the
initializer) to validate and/or clamp the Icon.emoji scale to a positive finite
value (e.g., reject/throw or replace with a sane default >0) using the same
rules as emojiScale(in:), so emojiIconFontSize(scale:) and TabBarView always
receive a valid finite >0 scale and Codable/Hashable semantics remain
consistent.
🤖 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/TabBarView.swift`:
- Around line 932-953: In splitActionButtonTooltip(_:tooltips:) the .custom(let
identifier) branch currently returns the raw identifier when button.tooltip is
missing; change this to avoid exposing internal identifiers by returning an
empty string (or a localized "help" placeholder) instead and optionally emit a
debug-only assertion/log mentioning BonsplitConfiguration.SplitActionButton and
the identifier; update the .custom case in splitActionButtonTooltip to return ""
(and add a debug assert/log) rather than returning identifier.

---

Nitpick comments:
In `@Sources/Bonsplit/Public/BonsplitConfiguration.swift`:
- Around line 349-374: SplitButtonBackdropEffect is currently Sendable but not
Equatable which prevents using it with SwiftUI onChange/== and tests; add
Equatable conformance to the struct declaration (public struct
SplitButtonBackdropEffect: Sendable, Equatable) so the compiler can synthesize
== (all stored properties are already Equatable), and mirror this approach for
related appearance value types (e.g., any new Appearance properties) so they are
also Equatable and uniformly comparable.
- Around line 274-284: The SplitActionButton initializer currently accepts
Icon.emoji values without validating the emoji scale, which can allow non-finite
or non-positive scales (NaN/0/negative) to leak into Equatable/Hashable and into
UI sizing; update SplitActionButton.init (or add a small factory/validator used
by the initializer) to validate and/or clamp the Icon.emoji scale to a positive
finite value (e.g., reject/throw or replace with a sane default >0) using the
same rules as emojiScale(in:), so emojiIconFontSize(scale:) and TabBarView
always receive a valid finite >0 scale and Codable/Hashable semantics remain
consistent.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 61c5511f-f970-47b1-b596-579988dedd85

📥 Commits

Reviewing files that changed from the base of the PR and between b8a8e39 and 85e1e38.

📒 Files selected for processing (2)
  • Sources/Bonsplit/Internal/Views/TabBarView.swift
  • Sources/Bonsplit/Public/BonsplitConfiguration.swift

Comment on lines +932 to +953
private func splitActionButtonTooltip(
_ button: BonsplitConfiguration.SplitActionButton,
tooltips: BonsplitConfiguration.SplitButtonTooltips
) -> String {
if let tooltip = button.tooltip?.trimmingCharacters(in: .whitespacesAndNewlines),
!tooltip.isEmpty {
return tooltip
}

switch button.action {
case .newTerminal:
return tooltips.newTerminal
case .newBrowser:
return tooltips.newBrowser
case .splitRight:
return tooltips.splitRight
case .splitDown:
return tooltips.splitDown
case .custom(let identifier):
return identifier
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Custom-action tooltip falls back to the raw identifier.

For .custom(let identifier), when no explicit tooltip is set the user-facing tooltip becomes the raw identifier string (Line 951). For embedder-defined identifiers like "open-settings" or "com.acme.action.foo", that surfaces in the UI as the tooltip text. Consider either requiring a non-empty tooltip for custom buttons (assert/log in DEBUG) or returning an empty string / .help(...) no-op when the tooltip is missing for .custom, so hosts don't accidentally ship internal identifiers to end users.

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

In `@Sources/Bonsplit/Internal/Views/TabBarView.swift` around lines 932 - 953, In
splitActionButtonTooltip(_:tooltips:) the .custom(let identifier) branch
currently returns the raw identifier when button.tooltip is missing; change this
to avoid exposing internal identifiers by returning an empty string (or a
localized "help" placeholder) instead and optionally emit a debug-only
assertion/log mentioning BonsplitConfiguration.SplitActionButton and the
identifier; update the .custom case in splitActionButtonTooltip to return ""
(and add a debug assert/log) rather than returning identifier.

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

🧹 Nitpick comments (4)
Sources/Bonsplit/Public/BonsplitConfiguration.swift (2)

451-457: Confirm didSet-based dedup interacts cleanly with external mutation patterns.

The splitButtons setter dedupes via didSet { splitButtons = Self.uniqueSplitButtons(splitButtons) }. This is safe in Swift (re-assigning inside didSet does not infinitely recurse, and uniqueSplitButtons is idempotent), and the test testAppearanceKeepsFirstSplitActionButtonForDuplicateIds exercises both the initializer and post-init reassignment paths.

One minor caveat: if a host uses KeyPath-based bindings (e.g., a settings UI bound to appearance.splitButtons), the apparent "value committed" can differ from "value assigned" when duplicates are passed in. This is the documented behavior, but worth a one-line note in the doc-comment so embedder UIs don't get surprised.

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

In `@Sources/Bonsplit/Public/BonsplitConfiguration.swift` around lines 451 - 457,
The doc-comment for the public property splitButtons should mention that
duplicates are removed in its didSet via Self.uniqueSplitButtons, so the value
actually committed may differ from the value assigned (e.g., when using
KeyPath/binding-based UIs); update the comment for splitButtons to add a
single-line note stating that duplicate button ids are ignored and that
post-assignment deduplication may alter the value observed by bindings,
referencing splitButtons, SplitActionButton and uniqueSplitButtons to make the
behavior clear to embedders.

349-383: Direct property mutation can bypass the init's clamping.

SplitButtonBackdropEffect clamps fadeRampStartFraction to [0, 0.95], opacities/contentOcclusionFraction to [0, 1], and widths to >= 0 in init, but the properties are exposed as public var so a host can later assign effect.fadeRampStartFraction = 5.0 (which LinearGradient stops would reject), trailingOpacity = -1, etc. TabBarView re-clamps when reading these, so this isn't currently a runtime defect — but it makes the invariant fragile against future call sites that read directly.

Consider either making the stored properties let (and exposing a with(...) builder for mutation) or moving the clamping into per-property didSet observers so the contract holds across the entire lifetime of an instance.

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

In `@Sources/Bonsplit/Public/BonsplitConfiguration.swift` around lines 349 - 383,
The struct SplitButtonBackdropEffect exposes public var properties that can
bypass the clamping performed in init; change the API so the invariants cannot
be violated at runtime — either convert the stored properties (style, fadeWidth,
contentFadeWidth, solidWidth, fadeRampStartFraction, leadingOpacity,
trailingOpacity, contentOcclusionFraction, masksTabContent) to let and add a
fluent with(...) builder or factory method on SplitButtonBackdropEffect that
returns a new clamped instance, or if mutability must remain, add per-property
didSet clamps mirroring the init logic (use min/max clamps) to enforce ranges on
assignment; update the public static default accordingly and adjust any callers
(e.g., TabBarView) to use the new with(...) or rely on the immutable properties.
Sources/Bonsplit/Internal/Views/TabBarView.swift (2)

866-877: Prefer identity-based ForEach since SplitActionButton is Identifiable.

ForEach(buttons.indices, id: \.self) keys rows by ordinal position, so reordering or removing a button reuses identities by index and can lead to stale closures or unwanted animation transitions. Since SplitActionButton already conforms to Identifiable with a String id, iterating over the array directly gives stable identity-based diffing.

♻️ Proposed simplification
-        HStack(spacing: TabBarStyling.splitButtonsSpacing) {
-            ForEach(buttons.indices, id: \.self) { index in
-                let button = buttons[index]
-                Button {
-                    performSplitActionButton(button)
-                } label: {
-                    splitActionButtonIcon(button.icon)
-                }
-                .buttonStyle(SplitActionButtonStyle(appearance: appearance))
-                .safeHelp(splitActionButtonTooltip(button, tooltips: tooltips))
-            }
-        }
+        HStack(spacing: TabBarStyling.splitButtonsSpacing) {
+            ForEach(buttons) { button in
+                Button {
+                    performSplitActionButton(button)
+                } label: {
+                    splitActionButtonIcon(button.icon)
+                }
+                .buttonStyle(SplitActionButtonStyle(appearance: appearance))
+                .safeHelp(splitActionButtonTooltip(button, tooltips: tooltips))
+            }
+        }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Sources/Bonsplit/Internal/Views/TabBarView.swift` around lines 866 - 877, The
ForEach in TabBarView currently iterates over buttons.indices
(ForEach(buttons.indices, id: \.self)) which keys by index; change it to iterate
directly over the buttons array so SwiftUI uses each SplitActionButton's stable
Identifiable id (e.g., ForEach(buttons) { button in ... }). Update the loop body
to use the button value (not buttons[index]) and keep the existing calls to
performSplitActionButton(button), splitActionButtonIcon(button.icon), and
splitActionButtonTooltip(button, tooltips: tooltips), leaving the
SplitActionButtonStyle and safeHelp unchanged.

186-194: Intentional use of non-failable String(decoding:as:) — SwiftLint hint is a false positive here.

The SwiftLint warning (optional_data_string_conversion) suggests the failable String(bytes:encoding:) initializer, but testCurrentColorSVGImageDataRendersAsTemplateWithInvalidUTF8Suffix explicitly verifies that template detection still works when the trailing bytes are invalid UTF-8. Switching to the failable initializer would return nil on that input and break the test. The current implementation is correct — consider adding // swiftlint:disable:next optional_data_string_conversion so future contributors don't get nudged into a regression.

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

In `@Sources/Bonsplit/Internal/Views/TabBarView.swift` around lines 186 - 194, The
SwiftLint hint is a false positive for imageDataShouldRenderAsTemplate(_:): keep
the non-failable String(decoding:as:) usage to preserve behavior for invalid
UTF-8 suffixes; add a SwiftLint suppression comment immediately above the
function (e.g., // swiftlint:disable:next optional_data_string_conversion) so
the linter won't suggest switching to the failable initializer and risk breaking
testCurrentColorSVGImageDataRendersAsTemplateWithInvalidUTF8Suffix.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@Sources/Bonsplit/Internal/Views/TabBarView.swift`:
- Around line 866-877: The ForEach in TabBarView currently iterates over
buttons.indices (ForEach(buttons.indices, id: \.self)) which keys by index;
change it to iterate directly over the buttons array so SwiftUI uses each
SplitActionButton's stable Identifiable id (e.g., ForEach(buttons) { button in
... }). Update the loop body to use the button value (not buttons[index]) and
keep the existing calls to performSplitActionButton(button),
splitActionButtonIcon(button.icon), and splitActionButtonTooltip(button,
tooltips: tooltips), leaving the SplitActionButtonStyle and safeHelp unchanged.
- Around line 186-194: The SwiftLint hint is a false positive for
imageDataShouldRenderAsTemplate(_:): keep the non-failable String(decoding:as:)
usage to preserve behavior for invalid UTF-8 suffixes; add a SwiftLint
suppression comment immediately above the function (e.g., //
swiftlint:disable:next optional_data_string_conversion) so the linter won't
suggest switching to the failable initializer and risk breaking
testCurrentColorSVGImageDataRendersAsTemplateWithInvalidUTF8Suffix.

In `@Sources/Bonsplit/Public/BonsplitConfiguration.swift`:
- Around line 451-457: The doc-comment for the public property splitButtons
should mention that duplicates are removed in its didSet via
Self.uniqueSplitButtons, so the value actually committed may differ from the
value assigned (e.g., when using KeyPath/binding-based UIs); update the comment
for splitButtons to add a single-line note stating that duplicate button ids are
ignored and that post-assignment deduplication may alter the value observed by
bindings, referencing splitButtons, SplitActionButton and uniqueSplitButtons to
make the behavior clear to embedders.
- Around line 349-383: The struct SplitButtonBackdropEffect exposes public var
properties that can bypass the clamping performed in init; change the API so the
invariants cannot be violated at runtime — either convert the stored properties
(style, fadeWidth, contentFadeWidth, solidWidth, fadeRampStartFraction,
leadingOpacity, trailingOpacity, contentOcclusionFraction, masksTabContent) to
let and add a fluent with(...) builder or factory method on
SplitButtonBackdropEffect that returns a new clamped instance, or if mutability
must remain, add per-property didSet clamps mirroring the init logic (use
min/max clamps) to enforce ranges on assignment; update the public static
default accordingly and adjust any callers (e.g., TabBarView) to use the new
with(...) or rely on the immutable properties.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 0992b3a1-5eb2-4228-b4c8-971ff115a901

📥 Commits

Reviewing files that changed from the base of the PR and between 85e1e38 and 3bfa3de.

📒 Files selected for processing (4)
  • Sources/Bonsplit/Internal/Styling/TabBarColors.swift
  • Sources/Bonsplit/Internal/Views/TabBarView.swift
  • Sources/Bonsplit/Public/BonsplitConfiguration.swift
  • Tests/BonsplitTests/BonsplitTests.swift
🚧 Files skipped from review as they are similar to previous changes (1)
  • Sources/Bonsplit/Internal/Styling/TabBarColors.swift

@lawrencecchen
lawrencecchen merged commit 3ac045a into main Apr 27, 2026
5 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.

1 participant