Skip to content

Add tab icon and color customization - #196

Open
lawrencecchen wants to merge 10 commits into
mainfrom
feat-tab-background-updates
Open

lawrencecchen wants to merge 10 commits into
mainfrom
feat-tab-background-updates

Conversation

@lawrencecchen

@lawrencecchen lawrencecchen commented Aug 1, 2026 •

Copy link
Copy Markdown

Summary

  • add Tab Icon… to the tab right-click menu with a searchable SF Symbol catalog and validated custom symbol entry
  • add Tab Color with the workspace-style clear, custom color, and named swatch menu
  • render user-selected colors as a narrow leading accent rail while preserving the host-provided full background
  • let host apps supply a live palette and register custom colors
  • persist and migrate icon and color overrides through snapshots and splits, including drag previews
  • localize the controls in English and Japanese

Tests

Summary by CodeRabbit

  • New Features
    • Customize individual tab colors using a built-in palette or custom hex colors.
    • Choose tab icons from searchable symbols, enter custom symbols, or reset to defaults.
    • Tab colors and icons persist across updates, splits, dragging, and relaunches.
    • Improved tab previews, contrast, hover states, and color indicators.
    • Added keyboard shortcut to cycle the focused tab’s appearance in the example app.
  • Documentation
    • Documented tab appearance options and customization configuration.
  • Localization
    • Added English and Japanese labels for tab customization controls.

@coderabbitai

coderabbitai Bot commented Aug 1, 2026 •

Copy link
Copy Markdown

Review Change Stack

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

The PR adds per-tab background, icon, and accent metadata. It persists and propagates these values through tab creation, updates, transfers, and snapshots. It adds context-menu customization, color-aware rendering, icon selection, localization, documentation, tests, and an example debug command.

Changes

Per-tab appearance customization

Layer / File(s) Summary
Appearance metadata contract and persistence
Sources/Bonsplit/Public/Types/Tab.swift, Sources/Bonsplit/Internal/Models/TabItem.swift, Sources/Bonsplit/Public/Types/TabColorPaletteEntry.swift
Tab and TabItem expose optional background, icon, and accent metadata. Codable support includes legacy backgroundHexOverride decoding.
Controller propagation and transfer
Sources/Bonsplit/Public/BonsplitController.swift, Tests/BonsplitTests/BonsplitTests.swift, README.md, CHANGELOG.md
createTab and updateTab accept, clear, and preserve appearance metadata. Tests cover persistence, no-op updates, split-pane transfers, and documented API behavior.
Background-aware tab rendering
Sources/Bonsplit/Internal/Styling/TabAppearanceColor.swift, Sources/Bonsplit/Internal/Styling/TabBarColors.swift, Sources/Bonsplit/Internal/Styling/TabBarMetrics.swift, Sources/Bonsplit/Internal/Views/TabDragPreview.swift, Tests/BonsplitTests/BonsplitTests.swift
Hex colors support RGB and RGBA parsing, serialization, compositing, contrast selection, hover states, custom controls, color rails, and drag previews.
Appearance customization flow
Sources/Bonsplit/Internal/Views/TabContextMenuPresenter.swift, Sources/Bonsplit/Internal/Views/TabItemView.swift, Sources/Bonsplit/Internal/Views/TabBarView.swift, Sources/Bonsplit/Internal/Views/TabIconCustomizerView.swift, Sources/Bonsplit/Internal/Views/TabColorPickerView.swift, Sources/Bonsplit/Internal/Views/TabIconCatalog.swift, Sources/Bonsplit/Resources/*/Localizable.strings, Tests/BonsplitTests/BonsplitTests.swift
Context menus provide icon and color actions. SwiftUI sheets validate and apply selections. Views use centralized background-aware colors and display accent rails.
Example command support
Example/BonsplitExample/AppState.swift, Example/BonsplitExample/BonsplitExampleApp.swift
The example debug menu cycles the selected tab through three appearance states.

Estimated code review effort: 4 (Complex) | ~60 minutes

Sequence Diagram(s)

sequenceDiagram
  participant TabContextMenuPresenter
  participant TabItemView
  participant TabColorPickerView
  participant TabBarView
  participant BonsplitController
  TabContextMenuPresenter->>TabItemView: open tab color controls
  TabItemView->>TabColorPickerView: present custom color picker
  TabColorPickerView->>TabBarView: return selected hex color
  TabBarView->>BonsplitController: updateTab(colorHexOverride)
  BonsplitController->>TabItemView: update tab metadata and rendering
Loading
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 9.52% 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
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the pull request's primary change: per-tab icon and color customization.
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.
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat-tab-background-updates

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.

@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

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@Sources/Bonsplit/Internal/Views/TabItemView.swift`:
- Around line 604-605: Update the control color resolution in TabItemView: at
Sources/Bonsplit/Internal/Views/TabItemView.swift lines 604-605, use the
custom-aware audio hover foreground/surface pair; apply the same pair at lines
637-638 for zoom hover and lines 1072-1073 for close hover; at line 788, resolve
the pinned audio badge glyph against its custom-aware circle fill. Preserve the
existing control behavior while ensuring all glyphs and surfaces derive from the
tab’s custom background.
🪄 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 Plus

Run ID: 40679b1a-3596-44d1-abd2-7f5a2921122d

📥 Commits

Reviewing files that changed from the base of the PR and between 86151cf and 0459e25.

📒 Files selected for processing (11)
  • CHANGELOG.md
  • Example/BonsplitExample/AppState.swift
  • Example/BonsplitExample/BonsplitExampleApp.swift
  • README.md
  • Sources/Bonsplit/Internal/Models/TabItem.swift
  • Sources/Bonsplit/Internal/Styling/TabBarColors.swift
  • Sources/Bonsplit/Internal/Views/TabDragPreview.swift
  • Sources/Bonsplit/Internal/Views/TabItemView.swift
  • Sources/Bonsplit/Public/BonsplitController.swift
  • Sources/Bonsplit/Public/Types/Tab.swift
  • Tests/BonsplitTests/BonsplitTests.swift

Comment thread Sources/Bonsplit/Internal/Views/TabItemView.swift Outdated

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

3 issues found across 11 files

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/Views/TabItemView.swift">

<violation number="1" location="Sources/Bonsplit/Internal/Views/TabItemView.swift:579">
P3: The remote indicator (and audio glyph) renders `currentTabText.opacity(0.78)`. For custom-background tabs, `currentTabText` is already the contrast-resolved white/black selected for that background, so the extra 0.78 alpha can push an already-marginal contrast below readable. Consider lowering opacity only for non-custom backgrounds, or raising the base contrast threshold for custom backgrounds so the dimmed state stays legible.</violation>

<violation number="2" location="Sources/Bonsplit/Internal/Views/TabItemView.swift:604">
P2: Hovering the audio, zoom, or close affordances can also produce low-contrast glyphs on custom tabs: these branches use custom-background text while their hover circles remain appearance-level colors. The hover text and fill should be resolved from the same surface, or the hover fill should become tab-aware.</violation>
</file>

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

<violation number="1" location="Sources/Bonsplit/Internal/Styling/TabBarColors.swift:259">
P3: For a tab with a custom background, hovering lightens/darkens the background in `tabBackground` but the text color resolved by `effectiveTabTextColor` is computed once from the unadjusted base color. On hover, the background shifts brightness (e.g., dark colors lighten by 0.07) while the chosen black/white text stays fixed, so contrast can drop below the level the code intended to guarantee. Consider resolving the hover-adjusted color first and feeding that into the contrast decision (or applying the adjustment to both text and background together) so readability stays consistent across hover/selected states.</violation>
</file>

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

Comment thread Sources/Bonsplit/Internal/Styling/TabBarColors.swift Outdated
Comment thread Sources/Bonsplit/Internal/Views/TabItemView.swift Outdated
Comment thread Sources/Bonsplit/Internal/Views/TabItemView.swift Outdated
Comment thread Sources/Bonsplit/Internal/Styling/TabBarColors.swift Outdated

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

1 issue found across 11 files

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:116">
P3: A valid 8-digit per-tab color with alpha `00` is treated as if `backgroundHex` were nil. Preserving the parsed clear color here would keep transparent colors distinct from the explicit `.some(nil)` reset path and avoid applying the standard selected-tab fill unexpectedly.</violation>
</file>

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

Comment thread Sources/Bonsplit/Internal/Styling/TabBarColors.swift Outdated
@lawrencecchen lawrencecchen changed the title Add mutable per-tab backgrounds Add tab appearance customization Aug 1, 2026

@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

🧹 Nitpick comments (1)
Sources/Bonsplit/Internal/Styling/TabAppearanceColor.swift (1)

40-63: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Verify swatch legibility for near-transparent custom colors.

Added rail color conversion that resolves colors in sRGB and adjusts brightness and saturation before returning an NSColor. The alpha component is passed through unchanged from the input hex. railNSColor feeds colorSwatchImage in TabItemView.swift, TabColorPickerView, and TabDragPreview. If a user picks a highly transparent custom background (a scenario the PR explicitly supports), the resulting swatch/rail dot stays nearly invisible even though brightness and saturation are boosted, because alpha is not floored.

Confirm this matches the intended design (showing true transparency in the swatch) rather than an oversight. If swatches need to stay legible regardless of the underlying color's opacity, floor the alpha for swatch rendering, for example:

💡 Possible fix: floor alpha for rail/swatch rendering
         let boostedBrightness = min(1, max(brightness, 0.62) + ((1 - brightness) * 0.28))
         let boostedSaturation = saturation <= 0.08
             ? saturation
             : min(1, saturation + ((1 - saturation) * 0.12))
+        let boostedAlpha = max(alpha, 0.55)
         return NSColor(
             hue: hue,
             saturation: boostedSaturation,
             brightness: boostedBrightness,
-            alpha: alpha
+            alpha: boostedAlpha
         )
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Sources/Bonsplit/Internal/Styling/TabAppearanceColor.swift` around lines 40 -
63, Update railNSColor so the alpha used for rail and swatch rendering has an
appropriate minimum floor, while preserving the existing sRGB conversion and
brightness/saturation adjustments. Ensure highly transparent custom colors
remain legible in colorSwatchImage consumers without changing the original
color’s stored transparency.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@Sources/Bonsplit/Internal/Views/TabColorPickerView.swift`:
- Around line 42-46: Update the ColorPicker in TabColorPickerView to set
supportsOpacity to true, enabling users to select and preserve the alpha
component supported by TabAppearanceColor’s `#RRGGBBAA` parsing and serialization.

In `@Sources/Bonsplit/Public/Types/TabColorPaletteEntry.swift`:
- Around line 3-6: Add a separate stable, nonlocalized palette entry key to
TabColorPaletteEntry and use it for Identifiable identity and Hashable semantics
instead of the user-facing name; localize the displayed label and update the
context-menu rendering to use that localized label rather than entry.name,
preserving distinct entries that share a translated name but have different hex
values.

---

Nitpick comments:
In `@Sources/Bonsplit/Internal/Styling/TabAppearanceColor.swift`:
- Around line 40-63: Update railNSColor so the alpha used for rail and swatch
rendering has an appropriate minimum floor, while preserving the existing sRGB
conversion and brightness/saturation adjustments. Ensure highly transparent
custom colors remain legible in colorSwatchImage consumers without changing the
original color’s stored transparency.
🪄 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 Plus

Run ID: 0aa67a90-acfe-4366-8210-e028877cdcde

📥 Commits

Reviewing files that changed from the base of the PR and between cd1ab03 and 81c9dea.

📒 Files selected for processing (16)
  • README.md
  • Sources/Bonsplit/Internal/Models/TabItem.swift
  • Sources/Bonsplit/Internal/Styling/TabAppearanceColor.swift
  • Sources/Bonsplit/Internal/Views/TabBarView.swift
  • Sources/Bonsplit/Internal/Views/TabColorPickerView.swift
  • Sources/Bonsplit/Internal/Views/TabContextMenuPresenter.swift
  • Sources/Bonsplit/Internal/Views/TabDragPreview.swift
  • Sources/Bonsplit/Internal/Views/TabIconCatalog.swift
  • Sources/Bonsplit/Internal/Views/TabIconCustomizerView.swift
  • Sources/Bonsplit/Internal/Views/TabItemView.swift
  • Sources/Bonsplit/Public/BonsplitController.swift
  • Sources/Bonsplit/Public/Types/Tab.swift
  • Sources/Bonsplit/Public/Types/TabColorPaletteEntry.swift
  • Sources/Bonsplit/Resources/en.lproj/Localizable.strings
  • Sources/Bonsplit/Resources/ja.lproj/Localizable.strings
  • Tests/BonsplitTests/BonsplitTests.swift
🚧 Files skipped from review as they are similar to previous changes (6)
  • Sources/Bonsplit/Resources/ja.lproj/Localizable.strings
  • Sources/Bonsplit/Resources/en.lproj/Localizable.strings
  • Sources/Bonsplit/Public/Types/Tab.swift
  • Sources/Bonsplit/Internal/Views/TabDragPreview.swift
  • Sources/Bonsplit/Internal/Models/TabItem.swift
  • Tests/BonsplitTests/BonsplitTests.swift

Comment on lines +42 to +46
ColorPicker(
localized("tabColor.color", defaultValue: "Color"),
selection: $color,
supportsOpacity: false
)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

rg -n -C 2 'supportsOpacity|`#RRGGBBAA`|func hex' \
  Sources/Bonsplit/Internal/Views/TabColorPickerView.swift \
  Sources/Bonsplit/Internal/Styling/TabAppearanceColor.swift \
  Tests/BonsplitTests/BonsplitTests.swift

Repository: manaflow-ai/bonsplit

Length of output: 1518


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '--- TabAppearanceColor.swift ---\n'
sed -n '1,140p' Sources/Bonsplit/Internal/Styling/TabAppearanceColor.swift | cat -n

printf '\n--- TabColorPickerView.swift ---\n'
sed -n '1,90p' Sources/Bonsplit/Internal/Views/TabColorPickerView.swift | cat -n

printf '\n--- Relevant tests references ---\n'
rg -n -C 3 'TabAppearanceColor|tabColor|ColorPicker|alpha|opacity|`#RRGGBBAA`|`#RRGGBBAA`|supportsOpacity' Tests Sources | sed -n '1,220p'

Repository: manaflow-ai/bonsplit

Length of output: 249


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '--- TabAppearanceColor.swift ---'
sed -n '1,140p' Sources/Bonsplit/Internal/Styling/TabAppearanceColor.swift | cat -n

echo
echo '--- TabColorPickerView.swift ---'
sed -n '1,90p' Sources/Bonsplit/Internal/Views/TabColorPickerView.swift | cat -n

echo
echo '--- Relevant references ---'
rg -n -C 3 'TabAppearanceColor|tabColor|ColorPicker|alpha|opacity|`#RRGGBBAA`|`#RRGGBBAA`|supportsOpacity' Tests Sources | sed -n '1,260p'

Repository: manaflow-ai/bonsplit

Length of output: 27501


Enable alpha for custom tab colors.

supportsOpacity: false removes the opacity control, but TabAppearanceColor parses and serializes #RRGGBBAA values. Set supportsOpacity to true so users can apply an explicit alpha via #RRGGBBAA.

Proposed fix
             ColorPicker(
                 localized("tabColor.color", defaultValue: "Color"),
                 selection: $color,
-                supportsOpacity: false
+                supportsOpacity: true
             )
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
ColorPicker(
localized("tabColor.color", defaultValue: "Color"),
selection: $color,
supportsOpacity: false
)
ColorPicker(
localized("tabColor.color", defaultValue: "Color"),
selection: $color,
supportsOpacity: true
)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Sources/Bonsplit/Internal/Views/TabColorPickerView.swift` around lines 42 -
46, Update the ColorPicker in TabColorPickerView to set supportsOpacity to true,
enabling users to select and preserve the alpha component supported by
TabAppearanceColor’s `#RRGGBBAA` parsing and serialization.

Comment on lines +3 to +6
/// A named color shown in the tab color context-menu palette.
public struct TabColorPaletteEntry: Identifiable, Hashable, Sendable {
/// The user-facing color name.
public let name: String

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

rg -n -C 5 \
  '\bTabColorPaletteEntry\b|\bdefaultPalette\b|\bentry\.name\b|\bForEach\b' \
  Sources/Bonsplit/Internal/Views Sources/Bonsplit/Resources Tests

Repository: manaflow-ai/bonsplit

Length of output: 10320


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "## TabColorPaletteEntry.swift"
cat -n Sources/Bonsplit/Public/Types/TabColorPaletteEntry.swift

echo
echo "## TabItemView context menu section"
sed -n '1720,1765p' Sources/Bonsplit/Internal/Views/TabItemView.swift | cat -n

echo
echo "## Localization/defaultPalette references"
rg -n -C 3 'Color Palette|palette|Palette|Color|defaultPalette|TAB_COLOR|tab.color|tabColor|colorPaletteProvider|entry\.name|entry\.hex|Hashable|Identifiable' Sources Tests | head -n 240

Repository: manaflow-ai/bonsplit

Length of output: 24448


Use a separate stable entry key for identity and localization.

id is derived from name, while Name is user-facing and displayed as entry.name in the context menu. Identical labels can be supplied with different hex values, so hash equality and identifiable identity no longer match. Add a nonlocalized palette entry key for id/hashing, localize the displayed label, and render the menu title from the localized label instead of from entry.name.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Sources/Bonsplit/Public/Types/TabColorPaletteEntry.swift` around lines 3 - 6,
Add a separate stable, nonlocalized palette entry key to TabColorPaletteEntry
and use it for Identifiable identity and Hashable semantics instead of the
user-facing name; localize the displayed label and update the context-menu
rendering to use that localized label rather than entry.name, preserving
distinct entries that share a translated name but have different hex values.

@lawrencecchen lawrencecchen changed the title Add tab appearance customization Add tab icon and color customization Aug 1, 2026
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