Skip to content

Allow cmux sidebar theming from Ghostty config - #4749

Closed
austinywang wants to merge 13 commits into
mainfrom
issue-1759-allow-theming-of-cmux-sidebar-via
Closed

austinywang wants to merge 13 commits into
mainfrom
issue-1759-allow-theming-of-cmux-sidebar-via

Conversation

@austinywang

@austinywang austinywang commented May 26, 2026 •

Copy link
Copy Markdown
Contributor

Closes #1759.

Summary

  • Add cmux-specific Ghostty config keys for sidebar background, selection, foreground, muted text, border, accent, and notification badge colors.
  • Apply those colors through the existing sidebar settings snapshot path so row rendering stays value-driven.
  • Track Ghostty-managed sidebar defaults so removing config keys clears only values cmux previously applied and leaves user-changed settings alone.
  • Document the new Ghostty sidebar keys in docs/configuration.md.

Scope

  • This PR covers color theming as the first self-contained increment. The issue comment requesting sidebar font-size theming is left for follow-up.

Verification

  • git diff --check
  • Local tests/builds not run per repository and task policy; CI is the source of truth.

View with Codesmith Autofix with Codesmith
Need help on this PR? Tag @codesmith with what you need. Autofix is disabled.


Note

Medium Risk
Touches appearance persistence in UserDefaults and broad sidebar rendering paths; logic is heavily tested but wrong managed-key clearing could surprise users who mix manual and config-driven colors.

Overview
Adds Ghostty-driven sidebar color theming: new cmux-specific config keys (foreground, muted text, selection foreground, border, accent, notification badge, plus existing selection background) are parsed, resolved for light/dark pairs, and written into UserDefaults through an expanded applySidebarAppearanceToUserDefaults path that records prior writes under cmux.ghosttyConfig.sidebarAppearance.appliedValues.v1 so removing or changing config only clears values cmux still owns—not user-edited hex overrides.

The sidebar settings snapshot gains the new hex fields, and workspace rows use them for primary/muted text, selection foreground, active borders, progress tracks/fills, unread badges, drop indicators, and the trailing divider (via WindowChromeBorder color override). Accent and border fall back to existing cmux defaults when unset.

Docs (docs/configuration.md) document the keys; tests cover parsing, resolution, managed-default clearing, and invalid-config preservation.

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


Summary by cubic

Adds Ghostty-driven theming for the cmux sidebar with light/dark support and preserved user overrides. Also bounds sidebar row text to fixed line limits to prevent layout overflow and fixes muted detail color. Addresses Linear #1759.

  • New Features

    • New Ghostty keys: sidebar-background, sidebar-tint-opacity, sidebar-selection-background, sidebar-selection-foreground, sidebar-foreground, sidebar-muted-foreground, sidebar-border-color, sidebar-accent-color, sidebar-notification-badge-background (alias: sidebar-notification-badge-color).
    • UI applies these to default/muted/selection text, borders (row + trailing divider via WindowChromeBorder override), progress track/fill, drop indicators, and badges; selection/muted text fall back to configured foreground when unset.
    • Settings snapshot now includes hex values for all sidebar theme colors. Docs and tests updated.
  • Bug Fixes

    • Preserves user-edited values by tracking Ghostty-managed writes in cmux.ghosttyConfig.sidebarAppearance.appliedValues.v1, and clears only keys cmux set (including opacity), including when config keys are removed or switch from paired to single/dark-only.
    • Applies muted sidebar detail color to secondary labels and info messages; improves fallbacks to avoid unreadable text and ensures progress contrast.
    • Bounds row title, description, and metadata blocks to max lines to avoid unbounded growth.

Written for commit 4455b08. Summary will update on new commits.

Review in cubic

Summary by CodeRabbit

  • New Features

    • Added sidebar theme color customization via hex config (foreground, muted foreground, selection, border, and accent).
    • Sidebar accents and top drop indicator visuals now follow the configured theme, with improved active-state coloring.
    • Theme color support includes optional light/dark paired values for more precise styling.
  • Bug Fixes

    • Improved handling of managed sidebar theme defaults to preserve or clear values correctly when switching theme sources.
  • Documentation

    • Expanded the configuration guide to describe supported sidebar theme keys, including sidebar-notification-badge-color as an alias.

@vercel

vercel Bot commented May 26, 2026 •

Copy link
Copy Markdown

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

Project Deployment Actions Updated (UTC)
cmux Canceled Canceled Jun 14, 2026 10:15pm
cmux-staging Building Building Preview, Comment Jun 14, 2026 10:15pm

@coderabbitai

coderabbitai Bot commented May 26, 2026 •

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 9161ad13-207a-41e2-8cdc-5b4855084fc4

📥 Commits

Reviewing files that changed from the base of the PR and between 42db47b and 4455b08.

⛔ Files ignored due to path filters (1)
  • .github/swift-file-length-budget.tsv is excluded by !**/*.tsv
📒 Files selected for processing (1)
  • Sources/ContentView.swift

📝 Walkthrough

Walkthrough

Extends SidebarTabItemSettingsSnapshot with five optional hex color fields wired from GhosttyConfig defaults, and updates TabItemView to derive active-state text, selection, border, accent, and badge colors from those values. Sidebar row subcomponents are refactored to accept themed color inputs directly. WindowChromeBorder gains a colorOverride parameter consumed by a rewritten SidebarTrailingBorder. tabDropDelegateFactory signature changes to accept non-optional row height. Tests expand GhosttyConfig UserDefaults coverage for managed state and full color-set writes; docs add the new sidebar theme configuration keys.

Changes

Sidebar Theme Configuration & Rendering

Layer / File(s) Summary
SidebarTabItemSettingsSnapshot hex color fields
Sources/ContentView.swift
Adds foregroundColorHex, mutedForegroundColorHex, selectionForegroundColorHex, borderColorHex, and accentColorHex optional fields to SidebarTabItemSettingsSnapshot, populated from GhosttyConfig user defaults keys during initialization.
TabItemView color resolution and active-state styling
Sources/ContentView.swift
Introduces computed helper properties to convert snapshot hex strings to Color/NSColor, resolves selection foreground with alpha clamping, and applies configured foreground, muted foreground, border, and accent colors as primary sources for text, selection, border fill, unread badge, progress track, and drop indicator visuals, with fallbacks to prior defaults.
Sidebar row components and metadata theming
Sources/ContentView.swift
Updates SidebarWorkspaceDescriptionText, SidebarMetadataRows, and SidebarMetadataMarkdownBlocks to accept configured active/secondary foreground colors directly, removes isActive-based branching, adjusts line limits using new collapsed-entry and collapsed-block limit constants, and simplifies foreground coloring throughout.
TabItemView layout, event wiring, and UI refinements
Sources/ContentView.swift
Changes tabDropDelegateFactory closure signature to accept non-optional CGFloat row height, adds row-height probe state, refactors event wiring to pass non-optional height to drop delegate, updates pullRequestForegroundColor and active .info log-level color to use activeSecondaryColor unconditionally, adjusts close-workspace button action and title/detail rendering, and marks refreshWorkspaceSnapshot private.
WindowChromeBorder colorOverride and SidebarTrailingBorder wiring
Sources/ContentView.swift
Adds optional colorOverride: NSColor? to WindowChromeBorder for custom border fill, rewrites SidebarTrailingBorder to read the border hex from GhosttyConfig and pass it as the override, and updates the sidebar overlay to instantiate SidebarTrailingBorder with tabItemSettings.
GhosttyConfig sidebar theme color parsing and UserDefaults tests
cmuxTests/GhosttyConfigTests.swift
Adds SidebarBackgroundConfigTests for parsing and resolving sidebar color directives. Expands UserDefaults application tests to cover sidebarAppearanceAppliedDefaultsKey, validates managed-vs-user-edited retention and clearing across mode changes, preserves managed colors on invalid config, and verifies full sidebar theme color set write and clear behavior.
Configuration docs and dev tooling update
docs/configuration.md, CLAUDE.md
Adds "Ghostty sidebar theme keys" section documenting all supported sidebar color and opacity keys and the notification badge color alias. Updates CLAUDE.md Codex outputs example link text format to use my-tag: prefix.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~22 minutes

Poem

🐇 The sidebar now speaks Ghostty's tongue,
With hex-hued colors freshly strung—
Foreground, border, badge aligned,
Each theme and terminal combined,
A rabbit's work, forever young! 🎨

Possibly related PRs

  • manaflow-ai/cmux#6111: Both PRs directly modify Sources/ContentView.swift's TabItemView.tabDropDelegateFactory closure signature and row-height plumbing for sidebar drop-target computation.
  • manaflow-ai/cmux#6052: Both PRs rework sidebar drag-and-drop drop-delegate height handling and row event wiring in TabItemView.

Important

Pre-merge checks failed

Please resolve all errors before merging. Addressing warnings is optional.

❌ Failed checks (1 error, 1 warning)

Check name Status Explanation Resolution
Cmux Swift Blocking Runtime ❌ Error The PR introduces Task.sleep(nanoseconds: 150_000_000) in startWorkspaceHandoffIfNeeded(), a production function for workspace switching. Per swift-blocking-runtime.md rules, Task.sleep in producti... Replace Task.sleep timeout with a Timer, DispatchSourceTimer, or callback mechanism that waits for a real workspace readiness signal instead of a fixed delay.
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 (19 passed)
Check name Status Explanation
Title check ✅ Passed The pull request title directly and concisely captures the main change: enabling sidebar theming through Ghostty config.
Description check ✅ Passed The description covers all required sections: Summary, Verification, and more. However, the Testing and Demo Video sections from the template are missing.
Linked Issues check ✅ Passed All objectives from issue #1759 are met: nine new Ghostty config keys for sidebar theming are implemented, applied through the sidebar settings snapshot, documented in docs/configuration.md, and tested thoroughly.
Out of Scope Changes check ✅ Passed All changes align with the stated scope. The PR covers sidebar color theming (9 new keys), their parsing and resolution, UserDefaults management, UI integration, and documentation. Font-size theming is correctly deferred as out of scope.
Cmux Swift Actor Isolation ✅ Passed Changes are to existing file-scope SwiftUI View structs and a pure Equatable value model. No new actor isolation issues introduced; View types and immutable String properties are unproblematic per...
Cmux Expensive Synchronous Load ✅ Passed PR adds lightweight UserDefaults reads for sidebar color hex values (lines 9727-9731 in ContentView.swift) in SidebarTabItemSettingsSnapshot init. Pre-existing GhosttyConfig.load() calls unchanged...
Cmux Cache Substitution Correctness ✅ Passed The PR correctly handles cache substitution in the persistence path: applySidebarAppearanceToUserDefaults() always runs immediately after resolveSidebarAppearance() with freshly-resolved values...
Cmux No Hacky Sleeps ✅ Passed PR modifies only Swift code (ContentView.swift, GhosttyConfigTests.swift) and documentation (Markdown files). The runtime-no-hacky-sleeps rule applies only to TypeScript, JavaScript, shell, and non...
Cmux Algorithmic Complexity ✅ Passed All color hex properties added to SidebarTabItemSettingsSnapshot are populated via O(1) UserDefaults lookups. TabItemView color properties (configuredForegroundColor, etc.) are simple O(1) flatMap...
Cmux Swift Concurrency ✅ Passed PR #4749 adds sidebar color theming via Ghostty config without introducing legacy async patterns. ContentView.swift properties (foregroundColorHex, mutedForegroundColorHex, etc.) are synchronously...
Cmux Swift @Concurrent ✅ Passed PR introduces no new async functions, no @concurrent annotations, and properly calls existing @MainActor async code from MainActor context.
Cmux Swift File And Package Boundaries ✅ Passed PR adds 167 lines (under 250-line threshold) to existing oversized file (16808 lines) with small UI-only views, AppKit bridges, and Ghostty integration glue—all explicitly allowed cases.
Cmux Swift Logging ✅ Passed PR introduces no logging violations: no print, debugPrint, dump, or NSLog in production code; no MainActor-coupled Logger constants; no ad hoc file/stdout logging.
Cmux User-Facing Error Privacy ✅ Passed PR adds sidebar theming via Ghostty config keys with internal data structures and documented configuration. No user-facing error messages, vendor names, internal details, or sensitive information a...
Cmux Full Internationalization ✅ Passed PR adds configuration documentation and sidebar theming code with no new user-facing UI strings, no string catalog modifications, and changes limited to tests, developer docs, and configuration keys.
Cmux Swiftui State Layout ✅ Passed PR adds 5 hex color properties to SidebarTabItemSettingsSnapshot (immutable values), passed as a snapshot to TabItemView in LazyVStack. No new store references, @State, @StateObject, or @ObservedOb...
Cmux Architecture Rethink ✅ Passed PR introduces SidebarTabItemSettingsStore using @Published/@MainActor/observers pattern, which is a required SwiftUI-AppKit bridge for reactive color updates; follows codebase pattern (10+ similar...
Cmux Swift Auxiliary Window Close Shortcuts ✅ Passed PR adds sidebar color theming only (data properties, View components); does not create any new NSWindow, NSPanel, NSWindowController, SwiftUI Window, or WindowGroup instances requiring cmux.* ident...
Cmux Source Artifacts ✅ Passed All changed files per the PR summary are legitimate: Swift source code (Sources/ContentView.swift), tests (cmuxTests/GhosttyConfigTests.swift), and documentation (docs/configuration.md, CLAUDE.md)....
✨ 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-1759-allow-theming-of-cmux-sidebar-via

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.

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

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

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 95f67b7. Configure here.

Comment thread Sources/GhosttyConfig.swift Outdated
@greptile-apps

greptile-apps Bot commented May 26, 2026 •

Copy link
Copy Markdown
Contributor

Greptile Summary

Adds Ghostty-config-driven sidebar color theming: 8 new keys (sidebar-foreground, sidebar-muted-foreground, sidebar-selection-foreground, sidebar-border-color, sidebar-accent-color, sidebar-notification-badge-background, plus existing background/selection) are parsed, resolved for light/dark pairs, and written into UserDefaults through an expanded applySidebarAppearanceToUserDefaults path that records prior writes under a versioned appliedValues key so removing config only clears values cmux owns. Drop-target height estimation (SidebarWorkspaceRowDropMetrics) is replaced by live GeometryReader+@State rowHeight measurement in each row and group-header view.

  • Config parsing & UserDefaults (GhosttyConfig.swift): applySidebarColorIfConfigured preserves existing managed values when the new config value is an invalid hex; applySidebarBackgroundColor now routes through the safe clearManagedSidebarAppearanceValue guard for all three background variant keys; asymmetric dark:#xyz-only config correctly clears a previously managed light tint.
  • Sidebar rendering (ContentView.swift): SidebarTabItemSettingsSnapshot gains 5 new hex fields; TabItemView derived colors (activePrimaryTextColor, activeSecondaryColor, progress track/fill, badge, border, drop indicator) fall back through configured → system defaults. The isActive flag is removed from sub-components in favour of always routing through the configured foreground.
  • Drop-height measurement: both TabItemView and SidebarWorkspaceGroupHeaderView replace static estimates with a .background { GeometryReader } probe — the group-header probe uses the correct new { _, newHeight in } onChange form while TabItemView's probe uses the deprecated single-argument form.

Confidence Score: 4/5

Safe to merge with one issue to verify: a test covering the paired-theme + top-level foreground override interaction was silently deleted.

The managed-defaults logic is well-tested for the new paths. The deletion of testLoadHonorsPairedThemeAndTopLevelForegroundOverrideByColorScheme is unexplained — the production code path it exercised (paired theme + explicit foreground override) is untouched by this PR, so the test should still pass. Losing that guard without a comment or replacement reduces confidence that the interaction stays correct across future parse-order changes.

cmuxTests/GhosttyConfigTests.swift — deleted test with no corresponding production change or replacement.

Important Files Changed

Filename Overview
Packages/CmuxTerminalCore/Sources/CmuxTerminalCore/Config/GhosttyConfig.swift Adds 8 new sidebar color config keys with managed-defaults tracking; applySidebarColorIfConfigured correctly preserves existing values on invalid hex; background-key clearing logic now routes through the safe clearManagedSidebarAppearanceValue guard. File grew from ~1292 to 1592 lines, now tracked in the budget. No new actor-isolation or blocking-runtime issues.
Sources/ContentView.swift Extends SidebarTabItemSettingsSnapshot with 5 new hex fields; wires configured foreground/muted/selection/border/accent colors into TabItemView computed properties. Replaces static drop-height estimation with live GeometryReader+@State rowHeight measurement. One rowHeightProbe uses the deprecated single-arg onChange.
Sources/SidebarWorkspaceGroupHeaderView.swift Replaces static metrics.dropTargetHeight with live GeometryReader probe. Uses the correct new two-argument onChange { _, newHeight in } form.
Sources/SidebarWorkspaceRowDropMetrics.swift Entirely deleted; drop-target height estimation is replaced by live layout measurement via GeometryReader in each row view.
Sources/TabItemViewDropSupport.swift Entirely deleted; helper methods inlined into TabItemView or removed as part of the drop-metrics refactor.
cmuxTests/GhosttyConfigTests.swift Adds tests for new sidebar color parsing, light/dark resolution, managed-default preservation on invalid hex, and asymmetric dark-only background switching. Removes testLoadHonorsPairedThemeAndTopLevelForegroundOverrideByColorScheme without explanation or replacement — that test guarded a still-live production code path.
Sources/SidebarWorkspaceGroupHeaderMetrics.swift Removes dropTargetHeight computed property now that drop height is measured from live layout.
docs/configuration.md Documents the 9 new sidebar theming keys with examples for hex, light/dark pair, and tint-opacity syntax.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A["Ghostty config file\n(sidebar-foreground, sidebar-border-color, …)"] --> B["GhosttyConfig.parse()"]
    B --> C["rawSidebar* fields set"]
    C --> D["resolveSidebarAppearance(preferredColorScheme:)"]
    D --> E["sidebarForeground, sidebarBorderColor, … NSColor fields"]
    E --> F["applySidebarAppearanceToUserDefaults()"]
    F --> G{{"key in appliedValues?"}}
    G -->|"Yes — managed value\nmatches current"| H["UserDefaults.removeObject(forKey:)"]
    G -->|"No — user-edited\nor new value"| I["UserDefaults.set(hex, forKey:)"]
    I --> J["appliedValues dict updated"]
    J --> K["cmux.ghosttyConfig.sidebarAppearance\n.appliedValues.v1 persisted"]
    K --> L["SidebarTabItemSettingsSnapshot\nreads foregroundColorHex,\nborderColorHex, etc."]
    L --> M["TabItemView computed colors\nactivePrimaryTextColor / activeSecondaryColor\nsidebarAccentColor / WindowChromeBorder.colorOverride"]
Loading

Reviews (10): Last reviewed commit: "fix: apply muted sidebar detail color" | Re-trigger Greptile

coderabbitai[bot]
coderabbitai Bot previously requested changes May 26, 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

🤖 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 `@docs/configuration.md`:
- Around line 17-19: Update the "Ghostty sidebar theme keys" section to
explicitly state these are cmux-specific extensions (not standard Ghostty keys)
and add concrete format examples; mention that values use Ghostty-style hex
(e.g., `#RRGGBB`) and show example usages including a single-mode example like
"sidebar-bg: `#123ABC`" and a light/dark pair like "sidebar-text:
light:`#111111`,dark:`#FFFFFF`" so theme authors understand scope and exact formats.

In `@Sources/GhosttyConfig.swift`:
- Around line 209-303: applySidebarAppearanceToUserDefaults creates a local
defaults = UserDefaults.standard but its helpers (applySidebarBackgroundColor,
applySidebarColorIfConfigured, clearManagedSidebarAppearanceValue and any other
helper that reads/writes defaults) call UserDefaults.standard directly, causing
inconsistent access; update those helper signatures to accept a defaults:
UserDefaults parameter (or alternatively return to using UserDefaults.standard
everywhere) and change calls in applySidebarAppearanceToUserDefaults to pass the
local defaults variable (also update any other call sites to the new
signatures), so all reads/writes use the same UserDefaults instance.
🪄 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: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: f0c2a2d7-05b0-4b35-b1a1-b468be960d9b

📥 Commits

Reviewing files that changed from the base of the PR and between e788e56 and bdf69de.

📒 Files selected for processing (5)
  • Sources/ContentView.swift
  • Sources/GhosttyConfig.swift
  • Sources/Sidebar/SidebarAppearanceSupport.swift
  • cmuxTests/GhosttyConfigTests.swift
  • docs/configuration.md

Comment thread docs/configuration.md Outdated
Comment thread Sources/GhosttyConfig.swift Outdated
@austinywang
austinywang dismissed coderabbitai[bot]’s stale review May 26, 2026 07:50

Dismissed after addressing the two CodeRabbit comments in fb2504d; both CodeRabbit review threads are resolved and the follow-up CodeRabbit summary completed with pre-merge checks passing.

@@ -721,70 +721,6 @@ final class GhosttyConfigTests: XCTestCase {
XCTAssertEqual(rgb255(darkConfig.backgroundColor), RGB(red: 0, green: 43, blue: 54))

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.

P1 Test coverage for paired-theme + foreground-override deleted without explanation

testLoadHonorsPairedThemeAndTopLevelForegroundOverrideByColorScheme was removed in this diff. The test exercised a specific interaction: a config that sets theme = light:X,dark:Y followed by a top-level foreground = #rrggbb override, asserting the override wins for both color schemes. No production code in GhosttyConfig.swift changed this behavior — the case "foreground" parse branch and paired-theme loadTheme call are untouched — so the test should still be valid. Silently dropping it without a comment or a replacement reduces confidence that this interaction continues to work if the parse order changes in a future PR.

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!

@lawrencecchen lawrencecchen added the stale-revisit Closed after 30+ days without activity; preserved for possible revisit or reopening. label Sep 23, 2026
@github-project-automation github-project-automation Bot moved this from Todo to Done in cmux backlog Sep 23, 2026

This branch was successfully deployed

1 active deployment
Preview – cmux — 4455b084 Deployed Jun 14, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

stale-revisit Closed after 30+ days without activity; preserved for possible revisit or reopening.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Allow theming of cmux sidebar via Ghostty config

3 participants