feat: auto-match sidebar background to Ghostty terminal theme#2232
feat: auto-match sidebar background to Ghostty terminal theme#2232christi4nity wants to merge 1 commit intomanaflow-ai:mainfrom
Conversation
When no explicit `sidebar-background` is configured, the sidebar now automatically uses the terminal's background color and opacity with material set to `none`, bypassing the glass/blur material that distorts the theme color. Runtime theme changes keep the sidebar in sync via the existing `refreshGhosttyAppearanceConfig` notification flow. Respects manual Settings UI overrides by skipping auto-sync when a non-default material has been explicitly chosen.
|
@christi4nity is attempting to deploy a commit to the Manaflow Team on Vercel. A member of the Team first needs to authorize it. |
📝 WalkthroughWalkthroughUpdated sidebar appearance logic to auto-sync from terminal theme when no custom background is configured. When Changes
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
Actionable comments posted: 3
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@cmuxTests/GhosttyConfigTests.swift`:
- Around line 2484-2515: The test
testApplyToUserDefaultsFallsBackToTerminalBackground asserts the sidebar tint
hex as "#1e1e2e" but the code stores backgroundColor.hexString() in UserDefaults
which returns an uppercase hex; update the expectation to the uppercase form
(e.g. "#1E1E2E") to match the value written by
GhosttyConfig.applySidebarAppearanceToUserDefaults (and the NSColor.hexString
implementation).
In `@Sources/GhosttyConfig.swift`:
- Line 181: The saved sidebar opacity currently uses "sidebarTintOpacity ??
backgroundOpacity" which can write an out-of-range backgroundOpacity; update
GhosttyConfig.swift so the value persisted via defaults.set(...) is clamped to
the valid range (e.g., 0.0...1.0). Specifically, when calling defaults.set(...)
for sidebarTintOpacity, compute the fallback as a clampedBackgroundOpacity
(apply the same clamping logic used for sidebarTintOpacity to backgroundOpacity)
and use that clamped value instead of raw backgroundOpacity; reference the
variables sidebarTintOpacity, backgroundOpacity and the defaults.set(...) call
to locate and fix the code.
- Around line 169-171: The branch is incorrectly treating the user-selected
SidebarMaterialOption.none as auto-syncable; instead of overloading
SidebarMaterialOption.none, add and use an explicit auto-sync marker (e.g., a
new enum case or a separate Bool in settings) and change the condition around
currentMaterial in GhosttyConfig (the if that checks currentMaterial against
SidebarMaterialOption.none and SidebarMaterialOption.sidebar) to guard on that
new auto-sync flag (or enum value) rather than the .none rawValue; apply the
same change to the similar check at the other occurrence (the block around lines
178-182) so explicitly user-selected “None” is never overwritten during theme
refresh.
🪄 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: d25fcce2-4525-4455-a6ce-f5e84a44515f
📒 Files selected for processing (4)
Sources/ContentView.swiftSources/GhosttyConfig.swiftSources/WorkspaceContentView.swiftcmuxTests/GhosttyConfigTests.swift
| func testApplyToUserDefaultsFallsBackToTerminalBackground() { | ||
| let defaults = UserDefaults.standard | ||
| let testKey = "sidebarTintHex" | ||
| let original = defaults.string(forKey: testKey) | ||
| defer { restoreDefaultsValue(original, key: testKey, defaults: defaults) } | ||
| let keys = ["sidebarTintHex", "sidebarTintHexLight", "sidebarTintHexDark", | ||
| "sidebarTintOpacity", "sidebarMaterial"] | ||
| let originals = keys.map { defaults.object(forKey: $0) } | ||
| defer { | ||
| for (key, original) in zip(keys, originals) { | ||
| restoreDefaultsValue(original, key: key, defaults: defaults) | ||
| } | ||
| } | ||
|
|
||
| defaults.set("#AAAAAA", forKey: "sidebarTintHex") | ||
| defaults.set("#BBBBBB", forKey: "sidebarTintHexLight") | ||
| defaults.set("#CCCCCC", forKey: "sidebarTintHexDark") | ||
|
|
||
| var config = GhosttyConfig() | ||
| config.backgroundColor = NSColor(hex: "#1e1e2e")! | ||
| config.backgroundOpacity = 0.9 | ||
| config.applySidebarAppearanceToUserDefaults() | ||
|
|
||
| XCTAssertEqual(defaults.string(forKey: "sidebarTintHex"), "#1e1e2e", | ||
| "Should fall back to terminal background when rawSidebarBackground is nil") | ||
| XCTAssertNil(defaults.string(forKey: "sidebarTintHexLight"), | ||
| "Should clear stale light key") | ||
| XCTAssertNil(defaults.string(forKey: "sidebarTintHexDark"), | ||
| "Should clear stale dark key") | ||
| XCTAssertEqual(defaults.double(forKey: "sidebarTintOpacity"), 0.9, accuracy: 0.0001, | ||
| "Should use backgroundOpacity when sidebarTintOpacity is nil") | ||
| XCTAssertEqual(defaults.string(forKey: "sidebarMaterial"), | ||
| SidebarMaterialOption.none.rawValue, | ||
| "Should set material to none for solid color rendering") | ||
| } |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Verify hexString() output format by checking its implementation and existing test assertions
echo "=== Checking NSColor hexString() implementation ==="
ast-grep --pattern 'func hexString() -> String {
$$$
}'
echo ""
echo "=== Checking existing hexString() test assertions for case pattern ==="
rg -n 'hexString\(\).*#[A-Fa-f0-9]' --type swift | head -20Repository: manaflow-ai/cmux
Length of output: 516
🏁 Script executed:
# Find applySidebarAppearanceToUserDefaults() implementation
rg -n "func applySidebarAppearanceToUserDefaults" --type swift -A 30Repository: manaflow-ai/cmux
Length of output: 2559
🏁 Script executed:
# Read the complete test methods (2484-2564) to see all hex assertions
sed -n '2484,2564p' cmuxTests/GhosttyConfigTests.swiftRepository: manaflow-ai/cmux
Length of output: 3978
Fix hex case mismatch in test assertion.
Line 2504 expects lowercase "#1e1e2e", but hexString() returns uppercase hex per the implementation (line 178: defaults.set(backgroundColor.hexString(), ...)). The assertion will fail.
🐛 Fix: use uppercase hex
- XCTAssertEqual(defaults.string(forKey: "sidebarTintHex"), "#1e1e2e",
+ XCTAssertEqual(defaults.string(forKey: "sidebarTintHex"), "#1E1E2E",
"Should fall back to terminal background when rawSidebarBackground is nil")📝 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.
| func testApplyToUserDefaultsFallsBackToTerminalBackground() { | |
| let defaults = UserDefaults.standard | |
| let testKey = "sidebarTintHex" | |
| let original = defaults.string(forKey: testKey) | |
| defer { restoreDefaultsValue(original, key: testKey, defaults: defaults) } | |
| let keys = ["sidebarTintHex", "sidebarTintHexLight", "sidebarTintHexDark", | |
| "sidebarTintOpacity", "sidebarMaterial"] | |
| let originals = keys.map { defaults.object(forKey: $0) } | |
| defer { | |
| for (key, original) in zip(keys, originals) { | |
| restoreDefaultsValue(original, key: key, defaults: defaults) | |
| } | |
| } | |
| defaults.set("#AAAAAA", forKey: "sidebarTintHex") | |
| defaults.set("#BBBBBB", forKey: "sidebarTintHexLight") | |
| defaults.set("#CCCCCC", forKey: "sidebarTintHexDark") | |
| var config = GhosttyConfig() | |
| config.backgroundColor = NSColor(hex: "#1e1e2e")! | |
| config.backgroundOpacity = 0.9 | |
| config.applySidebarAppearanceToUserDefaults() | |
| XCTAssertEqual(defaults.string(forKey: "sidebarTintHex"), "#1e1e2e", | |
| "Should fall back to terminal background when rawSidebarBackground is nil") | |
| XCTAssertNil(defaults.string(forKey: "sidebarTintHexLight"), | |
| "Should clear stale light key") | |
| XCTAssertNil(defaults.string(forKey: "sidebarTintHexDark"), | |
| "Should clear stale dark key") | |
| XCTAssertEqual(defaults.double(forKey: "sidebarTintOpacity"), 0.9, accuracy: 0.0001, | |
| "Should use backgroundOpacity when sidebarTintOpacity is nil") | |
| XCTAssertEqual(defaults.string(forKey: "sidebarMaterial"), | |
| SidebarMaterialOption.none.rawValue, | |
| "Should set material to none for solid color rendering") | |
| } | |
| func testApplyToUserDefaultsFallsBackToTerminalBackground() { | |
| let defaults = UserDefaults.standard | |
| let keys = ["sidebarTintHex", "sidebarTintHexLight", "sidebarTintHexDark", | |
| "sidebarTintOpacity", "sidebarMaterial"] | |
| let originals = keys.map { defaults.object(forKey: $0) } | |
| defer { | |
| for (key, original) in zip(keys, originals) { | |
| restoreDefaultsValue(original, key: key, defaults: defaults) | |
| } | |
| } | |
| defaults.set("#AAAAAA", forKey: "sidebarTintHex") | |
| defaults.set("#BBBBBB", forKey: "sidebarTintHexLight") | |
| defaults.set("#CCCCCC", forKey: "sidebarTintHexDark") | |
| var config = GhosttyConfig() | |
| config.backgroundColor = NSColor(hex: "#1e1e2e")! | |
| config.backgroundOpacity = 0.9 | |
| config.applySidebarAppearanceToUserDefaults() | |
| XCTAssertEqual(defaults.string(forKey: "sidebarTintHex"), "#1E1E2E", | |
| "Should fall back to terminal background when rawSidebarBackground is nil") | |
| XCTAssertNil(defaults.string(forKey: "sidebarTintHexLight"), | |
| "Should clear stale light key") | |
| XCTAssertNil(defaults.string(forKey: "sidebarTintHexDark"), | |
| "Should clear stale dark key") | |
| XCTAssertEqual(defaults.double(forKey: "sidebarTintOpacity"), 0.9, accuracy: 0.0001, | |
| "Should use backgroundOpacity when sidebarTintOpacity is nil") | |
| XCTAssertEqual(defaults.string(forKey: "sidebarMaterial"), | |
| SidebarMaterialOption.none.rawValue, | |
| "Should set material to none for solid color rendering") | |
| } |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@cmuxTests/GhosttyConfigTests.swift` around lines 2484 - 2515, The test
testApplyToUserDefaultsFallsBackToTerminalBackground asserts the sidebar tint
hex as "#1e1e2e" but the code stores backgroundColor.hexString() in UserDefaults
which returns an uppercase hex; update the expectation to the uppercase form
(e.g. "#1E1E2E") to match the value written by
GhosttyConfig.applySidebarAppearanceToUserDefaults (and the NSColor.hexString
implementation).
| if let currentMaterial, | ||
| currentMaterial != SidebarMaterialOption.none.rawValue, | ||
| currentMaterial != SidebarMaterialOption.sidebar.rawValue { |
There was a problem hiding this comment.
Manual None is not actually protected here.
SidebarMaterialOption.none is a real Settings choice in Sources/ContentView.swift:13790-13823, but this branch treats .none as auto-sync-eligible. If a user explicitly picks None and customizes tint/opacity, the next theme refresh will overwrite those values. This needs a separate “auto-synced vs user-selected” marker instead of overloading the .none raw value.
Also applies to: 178-182
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@Sources/GhosttyConfig.swift` around lines 169 - 171, The branch is
incorrectly treating the user-selected SidebarMaterialOption.none as
auto-syncable; instead of overloading SidebarMaterialOption.none, add and use an
explicit auto-sync marker (e.g., a new enum case or a separate Bool in settings)
and change the condition around currentMaterial in GhosttyConfig (the if that
checks currentMaterial against SidebarMaterialOption.none and
SidebarMaterialOption.sidebar) to guard on that new auto-sync flag (or enum
value) rather than the .none rawValue; apply the same change to the similar
check at the other occurrence (the block around lines 178-182) so explicitly
user-selected “None” is never overwritten during theme refresh.
| defaults.set(backgroundColor.hexString(), forKey: "sidebarTintHex") | ||
| defaults.removeObject(forKey: "sidebarTintHexLight") | ||
| defaults.removeObject(forKey: "sidebarTintHexDark") | ||
| defaults.set(sidebarTintOpacity ?? backgroundOpacity, forKey: "sidebarTintOpacity") |
There was a problem hiding this comment.
Clamp the fallback opacity before persisting it.
sidebarTintOpacity is clamped on parse, but backgroundOpacity is not. With this fallback, an out-of-range background-opacity now gets written straight into the sidebar setting.
Suggested fix
- defaults.set(sidebarTintOpacity ?? backgroundOpacity, forKey: "sidebarTintOpacity")
+ let resolvedOpacity = min(max(sidebarTintOpacity ?? backgroundOpacity, 0), 1)
+ defaults.set(resolvedOpacity, forKey: "sidebarTintOpacity")📝 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.
| defaults.set(sidebarTintOpacity ?? backgroundOpacity, forKey: "sidebarTintOpacity") | |
| let resolvedOpacity = min(max(sidebarTintOpacity ?? backgroundOpacity, 0), 1) | |
| defaults.set(resolvedOpacity, forKey: "sidebarTintOpacity") |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@Sources/GhosttyConfig.swift` at line 181, The saved sidebar opacity currently
uses "sidebarTintOpacity ?? backgroundOpacity" which can write an out-of-range
backgroundOpacity; update GhosttyConfig.swift so the value persisted via
defaults.set(...) is clamped to the valid range (e.g., 0.0...1.0). Specifically,
when calling defaults.set(...) for sidebarTintOpacity, compute the fallback as a
clampedBackgroundOpacity (apply the same clamping logic used for
sidebarTintOpacity to backgroundOpacity) and use that clamped value instead of
raw backgroundOpacity; reference the variables sidebarTintOpacity,
backgroundOpacity and the defaults.set(...) call to locate and fix the code.
Greptile SummaryThis PR adds automatic sidebar-background synchronization with the active Ghostty terminal theme. When no explicit Confidence Score: 3/5Merging will silently overwrite user-configured sidebar opacity on every runtime theme change and makes manual "None" material indistinguishable from the auto-sync state; needs one of the two P1s resolved before shipping. The feature logic and rendering changes are correct, and the three new unit tests cover the happy path well. However two P1 issues affect existing users: (1) any user who manually tuned the sidebar opacity via Settings UI will have it reset to the terminal background opacity on each theme change because the write is now unconditional; (2) the "None" material sentinel is shared between "auto-synced" and "user explicitly chose None", so the custom colour protection only works for non-None materials. Both issues cause silent data-loss of user preferences in normal usage. Sources/GhosttyConfig.swift — guard condition and unconditional opacity write (lines 169–182) Important Files Changed
Sequence DiagramsequenceDiagram
participant G as GhosttyConfig
participant WCV as WorkspaceContentView
participant UD as UserDefaults
participant SB as SidebarBackdrop
Note over G: App launch / loadFromDisk
G->>G: resolveSidebarBackground()
G->>UD: applySidebarAppearanceToUserDefaults()
alt rawSidebarBackground == nil
G->>UD: check sidebarMaterial
alt material is nil / none / sidebar
UD-->>G: proceed with auto-sync
G->>UD: set sidebarTintHex = backgroundColor.hexString()
G->>UD: remove sidebarTintHexLight/Dark
G->>UD: set sidebarTintOpacity = sidebarTintOpacity ?? backgroundOpacity
G->>UD: set sidebarMaterial = none
else material is user override (e.g. hudWindow)
G-->>G: skip (respect user choice)
end
else rawSidebarBackground != nil
G->>UD: write explicit sidebar colors
end
Note over WCV: Runtime theme change
WCV->>G: refreshGhosttyAppearanceConfig(next)
WCV->>WCV: config = next
WCV->>WCV: applyGhosttyChrome(from: next)
alt next.rawSidebarBackground == nil
WCV->>UD: next.applySidebarAppearanceToUserDefaults()
end
Note over SB: SwiftUI re-render
UD-->>SB: @AppStorage sidebarMaterial = none
SB->>SB: materialOption?.material == nil
SB->>SB: else branch -> Color(nsColor: tintColor)
Reviews (1): Last reviewed commit: "feat: auto-match sidebar background to G..." | Re-trigger Greptile |
| if let currentMaterial, | ||
| currentMaterial != SidebarMaterialOption.none.rawValue, | ||
| currentMaterial != SidebarMaterialOption.sidebar.rawValue { | ||
| return |
There was a problem hiding this comment.
"None" material indistinguishable from auto-sync state
The guard allows auto-sync to proceed whenever currentMaterial is nil, "none", or "sidebar". Because auto-sync itself writes "none" to sidebarMaterial, every subsequent theme-change invocation will also pass through the guard and overwrite both sidebarTintHex and sidebarTintOpacity.
The problem surfaces for any user who manually chose None from the Settings UI material picker with a custom tint color: after a theme change, their custom hex (and opacity) are silently replaced by the terminal background. There's no way to distinguish "auto-sync wrote none" from "user explicitly chose none."
A light mitigation would be to introduce a separate sentinel key (e.g. sidebarAutoSyncEnabled) written by applySidebarAppearanceToUserDefaults and checked by the guard, so a deliberate user "none" choice can be respected.
| defaults.set(backgroundColor.hexString(), forKey: "sidebarTintHex") | ||
| defaults.removeObject(forKey: "sidebarTintHexLight") | ||
| defaults.removeObject(forKey: "sidebarTintHexDark") | ||
| defaults.set(sidebarTintOpacity ?? backgroundOpacity, forKey: "sidebarTintOpacity") |
There was a problem hiding this comment.
Auto-sync unconditionally overwrites user-configured opacity
sidebarTintOpacity is now always written on every auto-sync call, falling back to backgroundOpacity (which defaults to 1.0). The previous implementation only wrote this key when sidebarTintOpacity was explicitly present in the Ghostty config.
A user who adjusts the opacity slider in the Settings UI will have their preference reset on the next runtime theme change, because the material remains "none" after the first auto-sync (allowing the guard to pass through on every subsequent call). Users who had dialled down the opacity will find it silently reset to fully-opaque.
Consider only overwriting the opacity when the Ghostty config explicitly specifies one, and leaving the existing UserDefaults value untouched otherwise — matching the original guarded behaviour.
| if next.rawSidebarBackground == nil { | ||
| next.applySidebarAppearanceToUserDefaults() | ||
| } |
There was a problem hiding this comment.
applySidebarAppearanceToUserDefaults() already returns early in its first if rawSidebarBackground == nil block when the condition is false. The outer check here is not wrong, but it adds noise and could mislead future readers into thinking the method doesn't internally handle the non-nil case.
A brief inline comment explaining why the guard is intentional (i.e., "we deliberately skip re-writing explicit sidebar colours on every theme event") would make the intent clearer without requiring a change to the method itself.
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!
There was a problem hiding this comment.
3 issues found across 4 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="cmuxTests/GhosttyConfigTests.swift">
<violation number="1" location="cmuxTests/GhosttyConfigTests.swift:2504">
P2: Test expectation uses lowercase hex while app serialization outputs uppercase, causing false-negative failures.</violation>
<violation number="2" location="cmuxTests/GhosttyConfigTests.swift:2519">
P2: Test cleanup is incomplete: it restores only two UserDefaults keys, but the exercised auto-sync path mutates additional sidebar keys, risking cross-test state leakage/flakiness.</violation>
</file>
<file name="Sources/GhosttyConfig.swift">
<violation number="1" location="Sources/GhosttyConfig.swift:170">
P1: Treating `.none` as auto-sync-eligible causes manual “None” material selections to be overwritten during theme refresh. Add a distinct auto-sync marker (or otherwise differentiate user-selected `.none`) so custom tint/opacity values are preserved.</violation>
</file>
Since this is your first cubic review, here's how it works:
- cubic automatically reviews your code and comments on bugs and improvements
- Teach cubic by replying to its comments. cubic learns from your replies and gets better over time
- Add one-off context when rerunning by tagging
@cubic-dev-aiwith guidance or docs links (includingllms.txt) - Ask questions if you need clarification on any suggestion
Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.
| // Settings, respect their choice and skip auto-sync. | ||
| let currentMaterial = defaults.string(forKey: "sidebarMaterial") | ||
| if let currentMaterial, | ||
| currentMaterial != SidebarMaterialOption.none.rawValue, |
There was a problem hiding this comment.
P1: Treating .none as auto-sync-eligible causes manual “None” material selections to be overwritten during theme refresh. Add a distinct auto-sync marker (or otherwise differentiate user-selected .none) so custom tint/opacity values are preserved.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At Sources/GhosttyConfig.swift, line 170:
<comment>Treating `.none` as auto-sync-eligible causes manual “None” material selections to be overwritten during theme refresh. Add a distinct auto-sync marker (or otherwise differentiate user-selected `.none`) so custom tint/opacity values are preserved.</comment>
<file context>
@@ -160,10 +160,26 @@ struct GhosttyConfig {
+ // Settings, respect their choice and skip auto-sync.
+ let currentMaterial = defaults.string(forKey: "sidebarMaterial")
+ if let currentMaterial,
+ currentMaterial != SidebarMaterialOption.none.rawValue,
+ currentMaterial != SidebarMaterialOption.sidebar.rawValue {
+ return
</file context>
|
|
||
| func testApplyToUserDefaultsSkipsAutoSyncWhenUserChoseCustomMaterial() { | ||
| let defaults = UserDefaults.standard | ||
| let keys = ["sidebarTintHex", "sidebarMaterial"] |
There was a problem hiding this comment.
P2: Test cleanup is incomplete: it restores only two UserDefaults keys, but the exercised auto-sync path mutates additional sidebar keys, risking cross-test state leakage/flakiness.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At cmuxTests/GhosttyConfigTests.swift, line 2519:
<comment>Test cleanup is incomplete: it restores only two UserDefaults keys, but the exercised auto-sync path mutates additional sidebar keys, risking cross-test state leakage/flakiness.</comment>
<file context>
@@ -2481,19 +2481,86 @@ final class SidebarBackgroundConfigTests: XCTestCase {
+
+ func testApplyToUserDefaultsSkipsAutoSyncWhenUserChoseCustomMaterial() {
+ let defaults = UserDefaults.standard
+ let keys = ["sidebarTintHex", "sidebarMaterial"]
+ let originals = keys.map { defaults.object(forKey: $0) }
+ defer {
</file context>
| config.backgroundOpacity = 0.9 | ||
| config.applySidebarAppearanceToUserDefaults() | ||
|
|
||
| XCTAssertEqual(defaults.string(forKey: "sidebarTintHex"), "#1e1e2e", |
There was a problem hiding this comment.
P2: Test expectation uses lowercase hex while app serialization outputs uppercase, causing false-negative failures.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At cmuxTests/GhosttyConfigTests.swift, line 2504:
<comment>Test expectation uses lowercase hex while app serialization outputs uppercase, causing false-negative failures.</comment>
<file context>
@@ -2481,19 +2481,86 @@ final class SidebarBackgroundConfigTests: XCTestCase {
+ config.backgroundOpacity = 0.9
+ config.applySidebarAppearanceToUserDefaults()
+
+ XCTAssertEqual(defaults.string(forKey: "sidebarTintHex"), "#1e1e2e",
+ "Should fall back to terminal background when rawSidebarBackground is nil")
+ XCTAssertNil(defaults.string(forKey: "sidebarTintHexLight"),
</file context>
| XCTAssertEqual(defaults.string(forKey: "sidebarTintHex"), "#1e1e2e", | |
| XCTAssertEqual(defaults.string(forKey: "sidebarTintHex"), "#1E1E2E", |
Summary
sidebar-backgroundis configured, the sidebar now automatically uses the terminal's Ghostty theme background color and opacitynone) so the sidebar color matches the terminal exactly — no visual distortion fromNSVisualEffectViewrefreshGhosttyAppearanceConfignotification flowChanges
Sources/ContentView.swiftSidebarBackdropto render solidColorwhen material isnoneSources/GhosttyConfig.swiftSources/WorkspaceContentView.swiftcmuxTests/GhosttyConfigTests.swiftTest plan
sidebar-backgroundin Ghostty configsidebar-backgroundin config still worksSummary by cubic
Auto-match the sidebar background to the active Ghostty terminal theme when no sidebar color is set. Uses a solid color (material set to none) for an exact match, stays in sync on theme changes, and respects manual material choices.
SidebarBackdropnow renders a solid color when material is none; tests cover fallback and override cases.Written for commit 472f08f. Summary will update on new commits.
Summary by CodeRabbit
Release Notes
Bug Fixes
Improvements