Skip to content

Fix transparent background flash during sidebar toggle - #2378

Merged
lawrencecchen merged 6 commits into
mainfrom
feat-layer-bg
Mar 31, 2026
Merged

lawrencecchen merged 6 commits into
mainfrom
feat-layer-bg

Conversation

@lawrencecchen

@lawrencecchen lawrencecchen commented Mar 31, 2026 •

Copy link
Copy Markdown
Contributor

Summary

  • Moves terminal background rendering from the Metal GPU pass to a CALayer (backgroundView), which resizes instantly with its parent NSView
  • Adds macos-background-from-layer config flag to Ghostty fork that sets bg_color alpha to 0, letting the host layer provide the background without alpha double-stacking
  • Simplifies titlebar and sidebar opacity formulas (single layer instead of two stacked semi-transparent layers)

The root cause: during sidebar toggle, there's a 3-5 frame gap between SwiftUI layout expansion and Metal re-render. With backgroundView clear in transparent mode, the desktop was visible through the near-clear window background. Now backgroundView always provides the terminal color, and the GPU bg pass is disabled.

Test plan

  • Toggle sidebar with background-opacity < 1 (e.g. 0.5, 0.8). Verify no flash of desktop/wallpaper
  • Verify terminal background color and opacity look correct in steady state
  • Verify titlebar color matches terminal background in transparent mode
  • Verify sidebar "match terminal background" mode matches terminal opacity
  • Verify per-cell backgrounds (e.g. Neovim with colored backgrounds) render correctly
  • Verify background blur still works with transparency
  • Verify opaque mode (background-opacity = 1) still works normally

Summary by cubic

Fixes the transparent background flash during sidebar toggles by moving the terminal background to a CALayer that resizes instantly. Disables Ghostty’s fullscreen GPU background fill via macos-background-from-layer and uses the configured alpha for the titlebar and sidebar.

  • Bug Fixes

    • Eliminates the 3–5 frame desktop flash during sidebar toggles and layout transitions in transparent mode.
  • Refactors

    • Provide terminal background via backgroundView (CALayer) and inject macos-background-from-layer = true into both normal and fallback inline configs; Ghostty fork explicitly skips the fullscreen background draw while keeping cell compositing pass-through.
    • Simplify opacity: titlebar/sidebar use the configured alpha directly; remove the panel fill helper.
    • Update ghostty submodule, document the new flag, and pin GhosttyKit checksums (including the bg draw-call skip).

Written for commit 8965e94. Summary will update on new commits.

Summary by CodeRabbit

  • New Features

    • Added a config option to enable background-from-layer rendering.
  • Improvements

    • Simplified titlebar and sidebar opacity handling for more consistent visuals.
    • Terminal backgrounds now use direct layer color and opacity for more predictable rendering.
  • Documentation

    • Documented the background-from-layer option and noted rendering interactions.
  • Chores

    • Updated embedded subproject pointer and checksum list.

Move terminal background rendering from the Metal GPU pass to a
CALayer (backgroundView). The GPU bg_color pass is disabled via a
new Ghostty config flag (macos-background-from-layer). The CALayer
resizes instantly with its parent NSView, eliminating the 3-5 frame
gap where the desktop was visible through the transparent window
during sidebar toggles and layout transitions.

Also simplifies the titlebar and sidebar opacity formulas since
there is now a single background layer instead of two stacked
semi-transparent layers.
@lawrencecchen

Copy link
Copy Markdown
Contributor Author

@codex review

@vercel

vercel Bot commented Mar 31, 2026 •

Copy link
Copy Markdown

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

Project Deployment Actions Updated (UTC)
cmux Ready Ready Preview, Comment Mar 31, 2026 1:12am

@coderabbitai

coderabbitai Bot commented Mar 31, 2026 •

Copy link
Copy Markdown

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: eeb05aa3-3dc9-47a4-9593-b3830e3fb048

📥 Commits

Reviewing files that changed from the base of the PR and between 0ea489d and 8965e94.

📒 Files selected for processing (1)
  • scripts/ghosttykit-checksums.txt
✅ Files skipped from review due to trivial changes (1)
  • scripts/ghosttykit-checksums.txt

📝 Walkthrough

Walkthrough

Reworks background sourcing so macOS CALayers provide titlebar, sidebar, and terminal backgrounds: injects macos-background-from-layer into Ghostty configs, zeros GPU background alpha in renderer when enabled, and assigns configured opacity/color directly to CALayer backgrounds (removes composited-opacity math).

Changes

Cohort / File(s) Summary
Titlebar & Sidebar
Sources/ContentView.swift, Sources/.../SidebarBackdrop
Removed the previous "effective composited opacity" computation and now pass GhosttyApp.shared.defaultBackgroundOpacity directly to titlebar/sidebar background initializers; comments adjusted to reflect a single CALayer source.
Terminal View & Config Injection
Sources/GhosttyTerminalView.swift
Injects macos-background-from-layer = true into fallback and default Ghostty configs before finalization; sets terminal layer.backgroundColor and layer.isOpaque directly from provided color (removed helper that cleared near-opaque colors).
Renderer & Config Flag
src/config/Config.zig, src/renderer/generic.zig, docs/ghostty-fork.md
Added exported boolean macos-background-from-layer (default false); when enabled, renderer forces bg_color[3] = 0 per-frame to disable GPU background compositing; docs updated and conflict-check note added.
Submodule & Checksums
ghostty, scripts/ghosttykit-checksums.txt
Bumped ghostty submodule pointer and appended two checksum lines for GhosttyKit artifact pins.

Sequence Diagram(s)

sequenceDiagram
  participant App as App (ContentView)
  participant Ghostty as Ghostty Config
  participant Renderer as Metal Renderer
  participant Layer as Terminal CALayer

  App->>Ghostty: initialize/fallback config (inject macos-background-from-layer = true)
  Ghostty-->>App: finalized config (flag present)
  App->>Layer: set layer.backgroundColor & layer.isOpaque from configured color/opacity
  App->>Renderer: begin frame (renderer reads config)
  Renderer->>Renderer: if flag true -> set bg_color[3] = 0
  Renderer-->>Layer: draw frame with transparent GPU background
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Poem

🐰 I tucked a flag inside the frame,
One layer now holds all the name.
No doubled fog, no extra glaze,
The titlebar hums in clearer ways.
Hop, render, nibble — joy in phase.

🚥 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 summarizes the main fix: eliminating the transparent background flash during sidebar toggle by moving background rendering to a CALayer.
Description check ✅ Passed The PR description includes a summary of changes and reasons, a detailed test plan with specific verification steps, but lacks a demo video link and incomplete checklist items.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat-layer-bg

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.

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Can't wait for the next one!

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@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 3 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/GhosttyTerminalView.swift">

<violation number="1" location="Sources/GhosttyTerminalView.swift:7304">
P2: Unconditional translucent panel fill assumes layer-bg mode globally, but fallback config path skips that flag and can reintroduce alpha stacking.</violation>
</file>

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

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

greptile-apps Bot commented Mar 31, 2026 •

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR fixes a 3–5 frame "flash of desktop" that appeared during sidebar toggles when background-opacity < 1. The root cause was that backgroundView (a CALayer) was kept clear during transparent mode, leaving only the Metal GPU background pass to provide color — and the GPU pass can't update fast enough during layout transitions. The fix moves terminal background ownership entirely to backgroundView by:

  1. Always filling backgroundView with the configured terminal color + opacity (removing the alpha < 0.999 ? .clear : color conditional).
  2. Disabling the GPU full-screen background pass via a new macos-background-from-layer = true Ghostty fork config, which sets bg_color alpha to 0 so per-cell colors continue to render normally without double-stacking.
  3. Simplifying the titlebar and sidebar opacity formulas from the two-layer composite 1 - (1-α)² to plain α, which is now correct because only one semi-transparent layer exists.

The architecture is sound and the visual changes (opacity values for titlebar/sidebar) are intentionally different from before — users on non-opaque configs will see slightly lower effective opacity in the titlebar/sidebar area, matching the terminal itself more accurately.

  • Missing docs/ghostty-fork.md entry for the new macos-background-from-layer fork patch — required by the project's submodule workflow policy.
  • panelBackgroundFillColor is now a trivial identity function; could be inlined at its two call sites for clarity.

Confidence Score: 5/5

  • Safe to merge — the architectural change is logically correct and both remaining findings are P2 documentation/style issues.
  • No P0 or P1 issues found. The core fix (CALayer-owned background + disabled GPU bg pass) is internally consistent: backgroundView always carries the terminal color, the Metal layer is clear, and the GPU bg pass is disabled via the new Ghostty config. The titlebar/sidebar opacity simplification is mathematically correct for a single-layer model. The only gaps are missing docs/ghostty-fork.md documentation (P2) and a now-trivial identity helper function (P2), neither of which affects runtime behavior.
  • ghostty submodule and docs/ghostty-fork.md — the fork now carries an undocumented patch (macos-background-from-layer); the doc should be updated before the next rebase or Ghostty upstream merge to avoid confusion.

Important Files Changed

Filename Overview
Sources/GhosttyTerminalView.swift Two targeted changes: panelBackgroundFillColor simplified from conditional-clear to identity return (correct given GPU bg pass is now disabled), and macos-background-from-layer = true injected into Ghostty config after all user config loads so the CALayer is always the sole background provider.
Sources/ContentView.swift Titlebar and sidebar opacity formulas simplified from 1 - (1-α)² (two-layer composite) to plain α (single CALayer); mathematically correct given the architectural shift to a single backgroundView layer.
ghostty Submodule pointer bumped to add macos-background-from-layer flag; docs/ghostty-fork.md was not updated to document this new fork change, violating the project's submodule workflow policy.

Sequence Diagram

sequenceDiagram
    participant Desktop as Desktop/Wallpaper
    participant Window as NSWindow (transparent)
    participant BG as backgroundView (CALayer)
    participant Metal as GhosttyNSView (Metal/GPU)

    Note over BG,Metal: Before this PR (transparent mode)
    Desktop->>Window: visible through clear backgroundView
    Metal-->>Window: GPU renders semi-transparent bg (3-5 frame delay during layout)
    Note over Desktop,Metal: ⚠️ Flash: bg clear for 3-5 frames during sidebar toggle

    Note over BG,Metal: After this PR
    BG->>Window: always fills with terminalColor @ configured opacity (instant)
    Metal-->>Window: GPU renders ONLY text & per-cell colors (bg pass disabled via macos-background-from-layer=true)
    Note over Desktop,Metal: ✅ No flash: backgroundView always covers the area instantly
Loading

Comments Outside Diff (1)

  1. docs/ghostty-fork.md, line 89-103 (link)

    P2 docs/ghostty-fork.md not updated for new fork change

    The Ghostty submodule pointer has been bumped to 41e79606, which adds the macos-background-from-layer config flag. Per CLAUDE.md, docs/ghostty-fork.md must be kept up to date with every fork change and its conflict notes.

    The file currently ends at section 7 ("cmux theme picker helper hooks") with no mention of the new macos-background-from-layer flag. A new section 8 should document:

    • The commit SHA(s) in the ghostty fork
    • Files changed (likely src/renderer/Metal.zig or similar)
    • Summary: disables the GPU full-screen background pass when macos-background-from-layer = true and sets bg_color alpha to 0, allowing the host CALayer (backgroundView) to provide the background instantly during layout transitions

    This is required by the project's submodule workflow policy.

Reviews (1): Last reviewed commit: "Document macos-background-from-layer for..." | Re-trigger Greptile

Comment thread Sources/GhosttyTerminalView.swift Outdated
Comment on lines 7299 to 7305
private static func panelBackgroundFillColor(for terminalBackgroundColor: NSColor) -> NSColor {
// The Ghostty renderer already draws translucent terminal backgrounds. If we paint an
// additional translucent layer here, alpha stacks and appears effectively opaque.
terminalBackgroundColor.alphaComponent < 0.999 ? .clear : terminalBackgroundColor
// The Ghostty renderer's GPU bg pass is disabled (macos-background-from-layer),
// so this CALayer is the sole provider of the terminal background color.
// It resizes instantly with its parent, eliminating the flash of desktop
// that would otherwise appear during sidebar toggles and layout transitions.
terminalBackgroundColor
}

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 panelBackgroundFillColor is now a trivial identity wrapper

After the change, panelBackgroundFillColor(for:) simply returns its input unchanged. The two call sites (init and setBackgroundColor) pass the terminal color with alpha already embedded. Since there's no longer any conditional logic, the function adds indirection without transformation. Consider inlining it at its two call sites for clarity, or keeping the function but renaming it to signal its purpose (e.g., backgroundLayerColor(for:)) so future readers aren't surprised by the lack of transformation.

Suggested change
private static func panelBackgroundFillColor(for terminalBackgroundColor: NSColor) -> NSColor {
// The Ghostty renderer already draws translucent terminal backgrounds. If we paint an
// additional translucent layer here, alpha stacks and appears effectively opaque.
terminalBackgroundColor.alphaComponent < 0.999 ? .clear : terminalBackgroundColor
// The Ghostty renderer's GPU bg pass is disabled (macos-background-from-layer),
// so this CALayer is the sole provider of the terminal background color.
// It resizes instantly with its parent, eliminating the flash of desktop
// that would otherwise appear during sidebar toggles and layout transitions.
terminalBackgroundColor
}
private static func panelBackgroundFillColor(for terminalBackgroundColor: NSColor) -> NSColor {
// The Ghostty renderer's GPU bg pass is disabled (macos-background-from-layer),
// so this CALayer is the sole provider of the terminal background color.
// It resizes instantly with its parent, eliminating the flash of desktop
// that would otherwise appear during sidebar toggles and layout transitions.
return terminalBackgroundColor
}

(No functional change — just noting the wrapper can be inlined if desired.)

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!

…apper

- Inject macos-background-from-layer in the fallback config path too,
  preventing alpha double-stacking when user config is invalid
- Inline panelBackgroundFillColor (now an identity function) at its
  two call sites and remove the wrapper

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

🧹 Nitpick comments (1)
docs/ghostty-fork.md (1)

107-115: Add the Ghostty commit SHA for this fork patch.

Using only Branch: feat-layer-bg makes rebases/debugging harder; please include the exact commit ID (like other sections) so the patch is fully traceable.

Based on learnings: Ghostty submodule changes must be committed in the ghostty submodule and docs/ghostty-fork.md should stay current with fork changes/conflict notes.

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@ghostty`:
- Line 1: Add a checksum entry for the new ghostty submodule commit
41e796064e89eacabdf3a6729475e250a5518e7a to the ghosttykit checksums file so the
download script can resolve it: update scripts/ghosttykit-checksums.txt by
appending the mapping that pairs the commit hash
41e796064e89eacabdf3a6729475e250a5518e7a with the corresponding prebuilt archive
checksum (the value used by download-prebuilt-ghosttykit.sh), ensuring the
format matches the existing entries so download-prebuilt-ghosttykit.sh will
recognize and validate the new commit.
- Line 1: The submodule pointer was updated to commit
41e796064e89eacabdf3a6729475e250a5518e7a but that commit is not on the remote
manaflow-ai/ghostty main branch; push the local commit
41e796064e89eacabdf3a6729475e250a5518e7a to the manaflow-ai/ghostty main branch,
then update the submodule pointer in this repo to the pushed commit; finally,
verify and update docs/ghostty-fork.md to record the fork changes or any
merge/conflict notes related to this update so the submodule change is
documented.

In `@Sources/GhosttyTerminalView.swift`:
- Around line 1392-1400: The blur radius isn't cleared when switching back to an
opaque background; update the background update paths (e.g.
GhosttyApp.applyBackgroundToKeyWindow() and
GhosttyNSView.applyWindowBackgroundIfActive()) to always call
cmuxApplyBackgroundBlur(to: radius:) on every background change and pass radius
0 whenever the effective opacity is >= 1.0 or the configured blur radius is 0
(i.e. when blur is disabled), ensuring blur is explicitly cleared even on the
opaque branch.
🪄 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: 96ce9d4a-515e-46a1-ae1f-1d7ebf9dc75e

📥 Commits

Reviewing files that changed from the base of the PR and between 978dd2c and 28d8084.

📒 Files selected for processing (4)
  • Sources/ContentView.swift
  • Sources/GhosttyTerminalView.swift
  • docs/ghostty-fork.md
  • ghostty

Comment thread ghostty Outdated
Comment on lines +1392 to +1400
// cmux provides the terminal background via backgroundView (CALayer)
// instead of the GPU full-screen bg pass, so the layer can provide
// instant coverage during sidebar toggle and other layout transitions.
loadInlineGhosttyConfig(
"macos-background-from-layer = true",
into: config,
prefix: "cmux-layer-bg",
logLabel: "layer background"
)

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

Reset compositor blur when this path goes back to opaque.

Turning on macos-background-from-layer makes the host layer/window path responsible for the visible background, but GhosttyApp.applyBackgroundToKeyWindow() and GhosttyNSView.applyWindowBackgroundIfActive() still only touch the blur setter on the transparent branch. If the user moves from translucent back to opaque, the old blur radius can stay latched and bleed into the supposedly solid background. Please clear blur on every background update and pass 0 when opacity is opaque or blur is disabled.

Based on learnings: In Sources/GhosttyTerminalView.swift, CGS window background blur is stateful. Always call cmuxApplyBackgroundBlur(to: NSWindow, radius: Int) on background updates and pass radius 0 when blur should be disabled (opacity >= 1.0 or configured radius == 0).

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

In `@Sources/GhosttyTerminalView.swift` around lines 1392 - 1400, The blur radius
isn't cleared when switching back to an opaque background; update the background
update paths (e.g. GhosttyApp.applyBackgroundToKeyWindow() and
GhosttyNSView.applyWindowBackgroundIfActive()) to always call
cmuxApplyBackgroundBlur(to: radius:) on every background change and pass radius
0 whenever the effective opacity is >= 1.0 or the configured blur radius is 0
(i.e. when blur is disabled), ensuring blur is explicitly cleared even on the
opaque branch.

The bg_color uniform alpha is still zeroed for cell compositing (so
transparent cells pass through to the CALayer), but the fullscreen
background fill draw step is now explicitly skipped instead of relying
on alpha=0 as a no-op.
@lawrencecchen
lawrencecchen merged commit 543481c into main Mar 31, 2026
16 of 22 checks passed
@lawrencecchen
lawrencecchen deleted the feat-layer-bg branch March 31, 2026 02:08
bn-l pushed a commit to bn-l/cmux that referenced this pull request Apr 3, 2026
)

* Fix transparent background flash during sidebar toggle

Move terminal background rendering from the Metal GPU pass to a
CALayer (backgroundView). The GPU bg_color pass is disabled via a
new Ghostty config flag (macos-background-from-layer). The CALayer
resizes instantly with its parent NSView, eliminating the 3-5 frame
gap where the desktop was visible through the transparent window
during sidebar toggles and layout transitions.

Also simplifies the titlebar and sidebar opacity formulas since
there is now a single background layer instead of two stacked
semi-transparent layers.

* Document macos-background-from-layer fork change

* Pin GhosttyKit checksum for macos-background-from-layer

* Address review feedback: fix fallback config path, inline identity wrapper

- Inject macos-background-from-layer in the fallback config path too,
  preventing alpha double-stacking when user config is invalid
- Inline panelBackgroundFillColor (now an identity function) at its
  two call sites and remove the wrapper

* Address adversarial review: skip fullscreen bg draw call explicitly

The bg_color uniform alpha is still zeroed for cell compositing (so
transparent cells pass through to the CALayer), but the fullscreen
background fill draw step is now explicitly skipped instead of relying
on alpha=0 as a no-op.

* Pin GhosttyKit checksum for bg draw-call skip

---------

Co-authored-by: Lawrence Chen <lawrencecchen@users.noreply.github.com>

This branch was successfully deployed

1 active deployment
Preview — 8965e94b Deployed Mar 31, 2026 by vercel[bot]
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