Skip to content

Configurable surface tab bar font size#2645

Merged
austinywang merged 5 commits intomainfrom
issue-2643-sidebar-font-size
Apr 7, 2026
Merged

Configurable surface tab bar font size#2645
austinywang merged 5 commits intomainfrom
issue-2643-sidebar-font-size

Conversation

@austinywang
Copy link
Copy Markdown
Contributor

@austinywang austinywang commented Apr 6, 2026

Related to #2534

This PR switches the branch from the earlier sidebar-only work to the actual requested feature: configurable font sizing for the horizontal surface tab bar.

What changed:

  • Adds a new config key: surface-tab-bar-font-size = N
  • Default: 11
  • Threads that value into Bonsplit tab bar appearance so the horizontal surface tab labels use the configured size
  • Keeps the drag preview and tab-strip shortcut/zoom affordances derived from the same title size
  • Applies the new size on Ghostty config reloads and seeds new workspaces from the cached config

Notes:


Note

Low Risk
Low risk UI/appearance change that threads a new config value into Bonsplit; potential issues are limited to tab-strip sizing updates and workspace chrome refresh behavior.

Overview
Adds a new Ghostty config key, surface-tab-bar-font-size (default 11), and parses it into GhosttyConfig.surfaceTabBarFontSize.

Threads this value into Bonsplit appearance so surface tab titles use the configured font size, seeds new workspaces from the cached config, and updates Workspace.applyGhosttyChrome(from:) to no-op (and optionally log) when neither the chrome background nor tab-title font size changed.

Reviewed by Cursor Bugbot for commit 24adfd0. Bugbot is set up for automated code reviews on this repo. Configure here.

Summary by CodeRabbit

  • New Features

    • Added a configurable tab bar font size (default: 11) so you can adjust tab title sizing.
  • Bug Fixes

    • Workspace appearance now detects unchanged background and tab font settings to avoid unnecessary UI updates, reducing flicker and improving performance.

@vercel
Copy link
Copy Markdown

vercel bot commented Apr 6, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
cmux Ready Ready Preview, Comment Apr 7, 2026 2:04am

@cubic-dev-ai
Copy link
Copy Markdown

cubic-dev-ai bot commented Apr 6, 2026

This review could not be run because your cubic account has exceeded the monthly review limit. If you need help restoring access, please contact contact@cubic.dev.

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Apr 6, 2026

📝 Walkthrough

Walkthrough

Adds a new surfaceTabBarFontSize config field (default 11), wires it through config parsing and Workspace appearance construction/comparison, updates applyGhosttyChrome to consider tab font size when deciding no-ops, and bumps the vendored bonsplit submodule commit.

Changes

Cohort / File(s) Summary
Configuration
Sources/GhosttyConfig.swift
Add surfaceTabBarFontSize: CGFloat = 11 and parse surface-tab-bar-font-size (as Double) from config.
Workspace appearance logic
Sources/Workspace.swift
Propagate tabTitleFontSize from config into bonsplitAppearance(from:...), compute/compare next vs current background and tab font size in applyGhosttyChrome(from:reason:), return early on no-op, and add conditional logging around these values.
Vendored dependency
vendor/bonsplit
Update submodule commit hash to a newer commit to align with appearance/font-size changes.

Sequence Diagram(s)

sequenceDiagram
    participant Config as GhosttyConfig
    participant Workspace as Workspace
    participant Bonsplit as Bonsplit/Renderer

    Config->>Workspace: load() -> surfaceTabBarFontSize
    Workspace->>Workspace: bonsplitAppearance(from:, tabTitleFontSize:)
    Workspace->>Bonsplit: compute next background hex + tabTitleFontSize
    Bonsplit-->>Workspace: current appearance
    Workspace->>Workspace: compare current vs next (background, fontSize)
    alt changes needed
        Workspace->>Bonsplit: apply updated chrome (background, tab font size)
        Bonsplit-->>Workspace: confirmation
    else no-op
        Workspace-->>Config: no-op (early return)
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Poem

🐰 I nibbled config, found a size,
Tabs now sing with softer eyes.
From ghostty file into workspace bright,
Fonts aligned, the tabs sit right,
A little hop — UI feels light! ✨

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately and concisely summarizes the main change: making the surface tab bar font size configurable.
Description check ✅ Passed The pull request description comprehensively covers the changes, rationale, and dependencies with clear explanations of the feature and implementation approach.

✏️ 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 issue-2643-sidebar-font-size

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
Copy link
Copy Markdown
Contributor

greptile-apps bot commented Apr 6, 2026

Greptile Summary

Adds a sidebar-font-size config key (default 12.5) that drives all sidebar workspace typography via a new SidebarWorkspaceFontMetrics struct, replacing the previously hardcoded 12.5 pt value. The SidebarTabItemSettingsStore correctly invalidates the GhosttyConfig cache and refreshes its snapshot on ghosttyConfigDidReload, so live config reloads propagate to the sidebar without a restart.

Confidence Score: 5/5

Safe to merge; all changes are additive and the config-reload path is correct.

The parsing, defaults, and snapshot-refresh flow are all implemented correctly and consistently with existing patterns. No P0/P1 issues found — only a minor naming suggestion on the description property.

No files require special attention.

Important Files Changed

Filename Overview
Sources/GhosttyConfig.swift Adds sidebarFontSize: CGFloat = 12.5 property and parses the new sidebar-font-size config key alongside font-size; consistent with existing parsing patterns.
Sources/ContentView.swift Adds SidebarWorkspaceFontMetrics struct deriving all sidebar text sizes from sidebarFontSize; SidebarTabItemSettingsStore correctly invalidates cache then refreshes snapshot on config reload.

Sequence Diagram

sequenceDiagram
    participant U as Config file
    participant G as GhosttyConfig
    participant NC as NotificationCenter
    participant S as SidebarTabItemSettingsStore
    participant SS as SidebarTabItemSettingsSnapshot
    participant FM as SidebarWorkspaceFontMetrics

    U->>G: sidebar-font-size = N (parsed on disk load)
    G->>NC: post .ghosttyConfigDidReload
    NC->>S: observer fires (MainActor Task)
    S->>G: invalidateLoadCache()
    S->>SS: SidebarTabItemSettingsSnapshot(defaults:)
    SS->>G: GhosttyConfig.load() [cache miss → loadFromDisk]
    G-->>SS: sidebarFontSize: CGFloat
    SS->>FM: SidebarWorkspaceFontMetrics(sidebarFontSize:)
    FM-->>SS: primary / description / secondary / tertiary
    SS-->>S: new snapshot (fontMetrics updated)
    S->>S: guard nextSnapshot != snapshot → publish
Loading

Reviews (1): Last reviewed commit: "feat: configurable sidebar tab font size..." | Re-trigger Greptile


private struct SidebarWorkspaceFontMetrics: Equatable {
let primary: CGFloat
let description: CGFloat
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2 description property name shadows Swift convention

Naming a stored CGFloat property description can confuse readers because description is the canonical CustomStringConvertible / NSObject debug property. Any call-site reading fontMetrics.description looks like a debug-string access rather than a font size. A name like descriptionLabel or subtitle would be clearer.

Suggested change
let description: CGFloat
let subtitleLabel: CGFloat

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!

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (2)
Sources/GhosttyConfig.swift (1)

246-249: Parsing implementation is correct and consistent.

The sidebar-font-size config key parsing follows the exact same pattern as font-size (lines 242-245), which is good for consistency.

Optional: Consider adding bounds validation.

While not required for this implementation, you might consider adding bounds checking similar to sidebar-tint-opacity (line 310) to prevent extreme values. For example:

case "sidebar-font-size":
    if let size = Double(value) {
        sidebarFontSize = CGFloat(min(max(size, 6.0), 72.0))
    }

This is purely optional since the existing font-size also lacks validation and config files are user-controlled.

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

In `@Sources/GhosttyConfig.swift` around lines 246 - 249, The current parsing for
the "sidebar-font-size" config key correctly sets sidebarFontSize but lacks
bounds validation; update the switch case handling "sidebar-font-size" in
GhosttyConfig to clamp the parsed Double to a safe range (e.g., min 6.0, max
72.0) before assigning to sidebarFontSize (similar pattern as
sidebar-tint-opacity), so extreme values are prevented while keeping the
existing parsing behavior.
Sources/ContentView.swift (1)

9894-9901: Avoid another shared GhosttyConfig cache flush on reload.

Sources/WorkspaceContentView.swift already invalidates GhosttyConfig on .ghosttyConfigDidReload. Clearing it again here means one reload can fan out into repeated cache clears/loads across windows and sidebars. Refreshing from the already-reloaded config, or centralizing invalidation once at the source, would avoid that extra churn.

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

In `@Sources/ContentView.swift` around lines 9894 - 9901, The handler registered
in ghosttyConfigObserver is redundantly calling
GhosttyConfig.invalidateLoadCache() on .ghosttyConfigDidReload, causing
duplicate cache clears; remove the GhosttyConfig.invalidateLoadCache() call from
the NotificationCenter.default.addObserver closure (keep the Task `@MainActor` and
self?.refreshSnapshot()) so this view only refreshes from the already-reloaded
config and lets WorkspaceContentView or the centralized reload producer perform
the single cache invalidation.
🤖 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/ContentView.swift`:
- Around line 9894-9901: The handler registered in ghosttyConfigObserver is
redundantly calling GhosttyConfig.invalidateLoadCache() on
.ghosttyConfigDidReload, causing duplicate cache clears; remove the
GhosttyConfig.invalidateLoadCache() call from the
NotificationCenter.default.addObserver closure (keep the Task `@MainActor` and
self?.refreshSnapshot()) so this view only refreshes from the already-reloaded
config and lets WorkspaceContentView or the centralized reload producer perform
the single cache invalidation.

In `@Sources/GhosttyConfig.swift`:
- Around line 246-249: The current parsing for the "sidebar-font-size" config
key correctly sets sidebarFontSize but lacks bounds validation; update the
switch case handling "sidebar-font-size" in GhosttyConfig to clamp the parsed
Double to a safe range (e.g., min 6.0, max 72.0) before assigning to
sidebarFontSize (similar pattern as sidebar-tint-opacity), so extreme values are
prevented while keeping the existing parsing behavior.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: e74bfb9e-afa6-4f62-81f8-5d3d0ccb29e3

📥 Commits

Reviewing files that changed from the base of the PR and between 179b16c and ded00ba.

📒 Files selected for processing (2)
  • Sources/ContentView.swift
  • Sources/GhosttyConfig.swift

@austinywang austinywang changed the title Configurable sidebar tab font size Configurable surface tab bar font size Apr 7, 2026
Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

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 the current code and only fix it if needed.

Inline comments:
In `@Sources/Workspace.swift`:
- Around line 6778-6785: The code passes surfaceTabBarFontSize directly into
BonsplitConfiguration.Appearance via bonsplitAppearance, allowing zero or
negative values from config to make labels disappear; sanitize by normalizing
surfaceTabBarFontSize to a positive fallback before creating
BonsplitConfiguration.Appearance (e.g., clamp or use max(1,
surfaceTabBarFontSize) or a defined minimum) and use that sanitized value
wherever surfaceTabBarFontSize is read/stored (including the other appearance
construction block around lines 6796-6825) so Bonsplit always receives a valid
positive font size.
🪄 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: 0d7f07a3-7169-493f-b419-dc2182b08e6f

📥 Commits

Reviewing files that changed from the base of the PR and between ded00ba and 9494732.

📒 Files selected for processing (3)
  • Sources/GhosttyConfig.swift
  • Sources/Workspace.swift
  • vendor/bonsplit
✅ Files skipped from review due to trivial changes (2)
  • vendor/bonsplit
  • Sources/GhosttyConfig.swift

Comment on lines 6778 to 6785
private static func bonsplitAppearance(
from backgroundColor: NSColor,
backgroundOpacity: Double
backgroundOpacity: Double,
tabTitleFontSize: CGFloat = 11
) -> BonsplitConfiguration.Appearance {
BonsplitConfiguration.Appearance(
tabTitleFontSize: tabTitleFontSize,
splitButtonTooltips: Self.currentSplitButtonTooltips(),
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

Sanitize surfaceTabBarFontSize before it reaches Bonsplit.

This value is now written straight into BonsplitConfiguration.Appearance. Since Sources/GhosttyConfig.swift parses numeric values directly, a config like surface-tab-bar-font-size = 0 or -1 will make the tab labels unreadable/disappear on first paint and on reload. Please normalize to a positive fallback before comparing or storing it.

🩹 Suggested guardrail
+    private static func sanitizedSurfaceTabBarFontSize(_ size: CGFloat) -> CGFloat {
+        guard size > 0 else { return 11 }
+        return size
+    }
+
     private static func bonsplitAppearance(
         from backgroundColor: NSColor,
         backgroundOpacity: Double,
         tabTitleFontSize: CGFloat = 11
     ) -> BonsplitConfiguration.Appearance {
+        let tabTitleFontSize = Self.sanitizedSurfaceTabBarFontSize(tabTitleFontSize)
         BonsplitConfiguration.Appearance(
             tabTitleFontSize: tabTitleFontSize,
             splitButtonTooltips: Self.currentSplitButtonTooltips(),
             enableAnimations: false,
             chromeColors: .init(
@@
-        let nextTabTitleFontSize = config.surfaceTabBarFontSize
+        let nextTabTitleFontSize = Self.sanitizedSurfaceTabBarFontSize(config.surfaceTabBarFontSize)

Also applies to: 6796-6825

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

In `@Sources/Workspace.swift` around lines 6778 - 6785, The code passes
surfaceTabBarFontSize directly into BonsplitConfiguration.Appearance via
bonsplitAppearance, allowing zero or negative values from config to make labels
disappear; sanitize by normalizing surfaceTabBarFontSize to a positive fallback
before creating BonsplitConfiguration.Appearance (e.g., clamp or use max(1,
surfaceTabBarFontSize) or a defined minimum) and use that sanitized value
wherever surfaceTabBarFontSize is read/stored (including the other appearance
construction block around lines 6796-6825) so Bonsplit always receives a valid
positive font size.

Copy link
Copy Markdown

@cursor cursor bot left a comment

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

Bugbot Autofix is kicking off a free cloud agent to fix this issue. This run is complimentary, but you can enable autofix for all future PRs in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 9494732. Configure here.

let initialSurfaceTabBarFontSize = GhosttyConfig.load().surfaceTabBarFontSize
let appearance = Self.bonsplitAppearance(
from: GhosttyApp.shared.defaultBackgroundColor,
backgroundOpacity: GhosttyApp.shared.defaultBackgroundOpacity
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Refactoring left behind unused method overload

Low Severity

The applyGhosttyChrome(backgroundColor:backgroundOpacity:reason:) overload is now dead code. Its previous caller, applyGhosttyChrome(from config:), was refactored to inline logic for tabTitleFontSize updates, leaving this overload unused and missing font size handling.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 9494732. Configure here.

@austinywang austinywang merged commit 0bbaa48 into main Apr 7, 2026
17 checks passed
austinywang added a commit that referenced this pull request Apr 7, 2026
Lost-update race: persistWindowGeometry and saveSessionSnapshot both
wrote the displayConfigurations map with their own copies, racing on
UserDefaults. Both writers now go through sessionPersistenceQueue and
do read-merge-encode-write inside the queue (writePersistedWindowGeometry).

Per-display visibility: hasSufficientVisibleFrame previously unioned
intersections into a bbox, which falsely passed two disjoint slivers
across two displays. It now checks each display individually so a
window is only "reachable" if at least one display has a usable chunk.

LRU cap: bound the per-display geometry map at 8 entries so users
cycling through many docks/displays don't accumulate UserDefaults
indefinitely. The just-written fingerprint is always preserved.

Gate post-reconcile persist: handleScreenParametersDidChange now only
persists the primary window's frame after the new display set if it
actually reconciled the primary (skipping miniaturized/fullscreen
windows), so a stale off-screen frame can't get baked into the new
fingerprint.

Restore tab title font size plumbing: the prior commit removed the
surfaceTabBarFontSize wiring in Workspace.swift, silently reverting
PR #2645. The bonsplit API still exposes tabTitleFontSize, so the
plumbing is restored.

Add regression tests for the per-display visibility check and the
LRU eviction so the contracts are pinned.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant