Skip to content

Fix browser pane dark-mode leak on light pages - #2339

Closed
austinywang wants to merge 2 commits into
manaflow-ai:mainfrom
austinywang:issue-2337-browser-dark-mode-leak
Closed

austinywang wants to merge 2 commits into
manaflow-ai:mainfrom
austinywang:issue-2337-browser-dark-mode-leak

Conversation

@austinywang

@austinywang austinywang commented Mar 30, 2026 •

Copy link
Copy Markdown
Contributor

Summary

  • stop injecting color-scheme overrides or DOM theme metadata into browser panes
  • keep the Browser Theme setting and apply it only through native WKWebView appearance so the selected light/dark/system mode stays consistent
  • propagate the selected browser theme mode to popup webviews while preserving underPageBackgroundColor only for the unpainted/loading region

Testing

  • not run locally (per repo policy)
  • built tagged dev app with ./scripts/reload.sh --tag browser-dark-mode-leak --launch
  • rebuilt with ./scripts/reload.sh --tag browser-dark-mode-leak
  • rebuilt again after restoring theme mode with ./scripts/reload.sh --tag browser-dark-mode-leak

Closes #2337
Related to #2083

Summary by CodeRabbit

  • Improvements
    • Enhanced browser theme consistency across main windows and popup windows
    • Theme preferences now apply more reliably during startup and when changed by users
    • Streamlined theme application process for better reliability

@vercel

vercel Bot commented Mar 30, 2026

Copy link
Copy Markdown

@austinywang is attempting to deploy a commit to the Manaflow Team on Vercel.

A member of the Team first needs to authorize it.

@coderabbitai

coderabbitai Bot commented Mar 30, 2026 •

Copy link
Copy Markdown
📝 Walkthrough

Walkthrough

Refactored browser theme application to centralize WebKit appearance switching via BrowserThemeSettings.apply(), removed JavaScript DOM injection for color-scheme handling, and extended theme propagation to popup controllers to prevent dark mode color bleed on light-mode pages.

Changes

Cohort / File(s) Summary
Browser Theme Application
Sources/Panels/BrowserPanel.swift, Sources/Panels/BrowserPopupWindowController.swift
Centralized theme application logic, removing injected JavaScript manipulation of color-scheme attributes. Updated BrowserPanel.setBrowserThemeMode(_:) to propagate theme changes to popup controllers. Added BrowserPopupWindowController.setBrowserThemeMode(_:) for recursive popup theme propagation and initial theme application during WebView setup.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

Poem

🐰 A rabbit hops through color schemes so fine,
No more injection makes the text align,
Light stays light when scrolling down the page,
Dark mode bleeds belong to history's age! ✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 9.09% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed Title accurately summarizes the main change: fixing a dark-mode leak on light pages by stopping CSS/appearance overrides.
Description check ✅ Passed Description covers what changed and testing approach, but lacks specific details on manual verification and skips the demo video and bot review checklist.
Linked Issues check ✅ Passed Changes directly address #2337 by removing dark-mode CSS injection and appearance overrides while retaining loading background color.
Out of Scope Changes check ✅ Passed All changes are scoped to fixing the dark-mode leak: theme application refactoring, popup propagation, and CSS injection removal.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

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

No issues found across 3 files

@greptile-apps

greptile-apps Bot commented Mar 30, 2026 •

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR removes the browser-pane theme-override system that was forcing WKWebView.appearance and injecting color-scheme CSS into every loaded page, allowing embedded websites to control their own light/dark styling. The change is a clean deletion across three files: the BrowserThemeMode/BrowserThemeSettings enums and all related logic are removed from BrowserPanel.swift, the toolbar button/popover and all @AppStorage wiring are removed from BrowserPanelView.swift, and the Settings UI picker row is removed from cmuxApp.swift.

Key changes:

  • webView.appearance is never overridden; WKWebView inherits the system appearance naturally
  • color-scheme CSS injection via evaluateJavaScript is eliminated — fixes the dark-mode leak on light pages
  • underPageBackgroundColor is still set from GhosttyBackgroundTheme to prevent a white flash before content loads (intentionally preserved)
  • The companion test class BrowserThemeSettingsTests and testBrowserPanelThemeModeUpdatesWebViewAppearance in cmuxTests/BrowserConfigTests.swift reference deleted types/methods and will cause a compile failure in the unit-test target — these need to be deleted

Confidence Score: 4/5

Safe to merge after deleting the orphaned test class and test method that reference the deleted BrowserThemeSettings/BrowserThemeMode types — the unit-test target will not compile as-is

All three production-code files are clean and the change is self-consistent. The sole blocking issue is that cmuxTests/BrowserConfigTests.swift still contains BrowserThemeSettingsTests and testBrowserPanelThemeModeUpdatesWebViewAppearance, which reference types and methods that no longer exist, causing a compile error in the unit-test target. Once those are deleted the PR is ready to merge.

cmuxTests/BrowserConfigTests.swift — BrowserThemeSettingsTests class (lines 1049–1090) and testBrowserPanelThemeModeUpdatesWebViewAppearance (lines 1170–1181) reference deleted symbols

Important Files Changed

Filename Overview
Sources/Panels/BrowserPanel.swift Removes BrowserThemeMode/BrowserThemeSettings enums, the browserThemeMode stored property, setBrowserThemeMode(), and applyBrowserThemeModeIfNeeded() entirely; webView.appearance is no longer forced and color-scheme CSS injection is stopped
Sources/Panels/BrowserPanelView.swift Removes @AppStorage binding, @State for the theme menu, browserThemeModeButton toolbar button, browserThemeModePopover, and all onChange/onAppear logic wiring the theme mode setting to the panel
Sources/cmuxApp.swift Drops browserThemeMode @AppStorage property, SettingsPickerRow for Browser Theme, and associated reset/migration logic from SettingsView

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[BrowserPanel init / makeWebView] --> B[Set underPageBackgroundColor\nfrom GhosttyBackgroundTheme]
    A --> C[Set customUserAgent]
    B --> D[WKWebView loads page]
    D --> E{Website controls\nits own theme}
    E -->|light site| F[Renders in light mode]
    E -->|dark site| G[Renders in dark mode]
    E -->|respects prefers-color-scheme| H[Follows macOS system appearance]

    subgraph REMOVED ["Removed (was: BrowserThemeMode override)"]
        R1[Set webView.appearance\n.aqua / .darkAqua / nil]
        R2[Inject color-scheme CSS\nvia evaluateJavaScript]
        R3[Theme toolbar button\nin BrowserPanelView]
        R4[Browser Theme picker\nin SettingsView]
    end

    style REMOVED fill:#fee2e2,stroke:#ef4444
Loading

Comments Outside Diff (1)

  1. cmuxTests/BrowserConfigTests.swift, line 1049-1181 (link)

    P1 Orphaned test classes reference deleted types — build will fail

    The PR deleted BrowserThemeSettings, BrowserThemeMode, and setBrowserThemeMode() from production code, but the corresponding tests were not removed. This means the unit-test target will fail to compile:

    • BrowserThemeSettingsTests (lines 1049–1090) references BrowserThemeSettings.mode(defaults:), BrowserThemeSettings.defaultMode, BrowserThemeSettings.modeKey, BrowserThemeSettings.legacyForcedDarkModeEnabledKey, and BrowserThemeMode — all deleted.
    • testBrowserPanelThemeModeUpdatesWebViewAppearance (lines 1170–1181) calls panel.setBrowserThemeMode(.dark/.light/.system) — also deleted.

    The entire BrowserThemeSettingsTests class and the testBrowserPanelThemeModeUpdatesWebViewAppearance test method need to be deleted alongside the production code they cover.

Reviews (1): Last reviewed commit: "Fix browser pane dark-mode leak" | Re-trigger Greptile

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
Sources/Panels/BrowserPanel.swift (1)

2396-2458: ⚠️ Potential issue | 🔴 Critical

Update the removed theme API tests in the same PR.

This change removes the browser theme surface from BrowserPanel, but cmuxTests/BrowserConfigTests.swift still references BrowserThemeSettings, BrowserThemeMode, and BrowserPanel.setBrowserThemeMode(_:). As-is, the test target will stop compiling. Please delete or rewrite those tests to cover the new contract instead (no forced appearance / no injected color-scheme override).

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

In `@Sources/Panels/BrowserPanel.swift` around lines 2396 - 2458, Tests in
cmuxTests/BrowserConfigTests.swift still reference the removed theme APIs
(BrowserThemeSettings, BrowserThemeMode, BrowserPanel.setBrowserThemeMode(_:))
and must be updated; remove those references and either delete the obsolete
tests or rewrite them to assert the new contract: verify
BrowserPanel.makeWebView(profileID:websiteDataStore:) /
configureWebViewConfiguration(...) no longer forces WKWebView appearance or
injects color-scheme CSS and that underPageBackgroundColor is set to
GhosttyBackgroundTheme.currentColor(), and ensure no calls remain to
BrowserPanel.setBrowserThemeMode(_:), BrowserThemeSettings, or BrowserThemeMode.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In `@Sources/Panels/BrowserPanel.swift`:
- Around line 2396-2458: Tests in cmuxTests/BrowserConfigTests.swift still
reference the removed theme APIs (BrowserThemeSettings, BrowserThemeMode,
BrowserPanel.setBrowserThemeMode(_:)) and must be updated; remove those
references and either delete the obsolete tests or rewrite them to assert the
new contract: verify BrowserPanel.makeWebView(profileID:websiteDataStore:) /
configureWebViewConfiguration(...) no longer forces WKWebView appearance or
injects color-scheme CSS and that underPageBackgroundColor is set to
GhosttyBackgroundTheme.currentColor(), and ensure no calls remain to
BrowserPanel.setBrowserThemeMode(_:), BrowserThemeSettings, or BrowserThemeMode.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: d344422b-db61-4528-bf18-1da761f294cb

📥 Commits

Reviewing files that changed from the base of the PR and between 35cb42f and 5d82f7a.

📒 Files selected for processing (3)
  • Sources/Panels/BrowserPanel.swift
  • Sources/Panels/BrowserPanelView.swift
  • Sources/cmuxApp.swift
💤 Files with no reviewable changes (2)
  • Sources/Panels/BrowserPanelView.swift
  • Sources/cmuxApp.swift

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

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

Inline comments:
In `@Sources/Panels/BrowserPanel.swift`:
- Around line 214-223: BrowserThemeSettings.apply(_:to:) currently forces
webView.appearance for .light/.dark which contradicts the "do not force page
appearance" policy; change apply(_ mode: BrowserThemeMode, to webView:
WKWebView) to always set webView.appearance = nil (remove the .aqua/.darkAqua
branches) and stop calling applyBrowserThemeModeIfNeeded(...) for both main and
popup web views so no overrides are reapplied; also update the migration/loading
of browserThemeMode (where saved UserDefaults are read) to either clear the
legacy browserThemeMode preference during migration or ignore persisted values
and default to .system so upgraded users aren’t stuck with a forced appearance.
🪄 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: 67d18d99-47f6-4845-8e5e-07333fc31ba9

📥 Commits

Reviewing files that changed from the base of the PR and between 5d82f7a and a5f760e.

📒 Files selected for processing (2)
  • Sources/Panels/BrowserPanel.swift
  • Sources/Panels/BrowserPopupWindowController.swift

Comment on lines +214 to +223
static func apply(_ mode: BrowserThemeMode, to webView: WKWebView) {
switch mode {
case .system:
webView.appearance = nil
case .light:
webView.appearance = NSAppearance(named: .aqua)
case .dark:
webView.appearance = NSAppearance(named: .darkAqua)
}
}

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

🧩 Analysis chain

🏁 Script executed:

cd /tmp/repo && sed -n '214,223p' Sources/Panels/BrowserPanel.swift

Repository: manaflow-ai/cmux

Length of output: 119


🏁 Script executed:

cd /tmp/repo && sed -n '4944,4949p' Sources/Panels/BrowserPanel.swift

Repository: manaflow-ai/cmux

Length of output: 119


🏁 Script executed:

cd /tmp/repo && sed -n '5451,5453p' Sources/Panels/BrowserPanel.swift

Repository: manaflow-ai/cmux

Length of output: 119


🏁 Script executed:

cd /tmp/repo && sed -n '2486,2489p' Sources/Panels/BrowserPanel.swift

Repository: manaflow-ai/cmux

Length of output: 119


🏁 Script executed:

cd /tmp/repo && sed -n '2309,2311p' Sources/Panels/BrowserPanel.swift

Repository: manaflow-ai/cmux

Length of output: 119


🏁 Script executed:

pwd && git ls-files Sources/Panels/BrowserPanel.swift | head -5

Repository: manaflow-ai/cmux

Length of output: 112


🏁 Script executed:

wc -l /home/jailuser/git/Sources/Panels/BrowserPanel.swift

Repository: manaflow-ai/cmux

Length of output: 118


🏁 Script executed:

sed -n '214,223p' /home/jailuser/git/Sources/Panels/BrowserPanel.swift

Repository: manaflow-ai/cmux

Length of output: 394


🏁 Script executed:

sed -n '4944,4949p' /home/jailuser/git/Sources/Panels/BrowserPanel.swift

Repository: manaflow-ai/cmux

Length of output: 292


🏁 Script executed:

sed -n '5451,5453p' /home/jailuser/git/Sources/Panels/BrowserPanel.swift

Repository: manaflow-ai/cmux

Length of output: 174


🏁 Script executed:

grep -n "browserThemeMode" /home/jailuser/git/Sources/Panels/BrowserPanel.swift | head -20

Repository: manaflow-ai/cmux

Length of output: 366


🏁 Script executed:

grep -n "var browserThemeMode" /home/jailuser/git/Sources/Panels/BrowserPanel.swift

Repository: manaflow-ai/cmux

Length of output: 115


🏁 Script executed:

sed -n '2309,2311p' /home/jailuser/git/Sources/Panels/BrowserPanel.swift

Repository: manaflow-ai/cmux

Length of output: 142


🏁 Script executed:

grep -n "BrowserThemeSettings.mode()" /home/jailuser/git/Sources/Panels/BrowserPanel.swift

Repository: manaflow-ai/cmux

Length of output: 124


🏁 Script executed:

grep -n "class BrowserThemeSettings" /home/jailuser/git/Sources/Panels/BrowserPanel.swift

Repository: manaflow-ai/cmux

Length of output: 42


🏁 Script executed:

sed -n '185,230p' /home/jailuser/git/Sources/Panels/BrowserPanel.swift

Repository: manaflow-ai/cmux

Length of output: 1640


🏁 Script executed:

grep -n "static func mode()" /home/jailuser/git/Sources/Panels/BrowserPanel.swift

Repository: manaflow-ai/cmux

Length of output: 42


🏁 Script executed:

sed -n '2600,2620p' /home/jailuser/git/Sources/Panels/BrowserPanel.swift

Repository: manaflow-ai/cmux

Length of output: 1146


🏁 Script executed:

sed -n '2605,2620p' /home/jailuser/git/Sources/Panels/BrowserPanel.swift

Repository: manaflow-ai/cmux

Length of output: 795


🏁 Script executed:

sed -n '2614,2630p' /home/jailuser/git/Sources/Panels/BrowserPanel.swift

Repository: manaflow-ai/cmux

Length of output: 870


🏁 Script executed:

grep -n "applyBrowserThemeModeIfNeeded\|BrowserThemeSettings.apply" /home/jailuser/git/Sources/Panels/BrowserPanel.swift

Repository: manaflow-ai/cmux

Length of output: 403


🏁 Script executed:

sed -n '2700,2710p' /home/jailuser/git/Sources/Panels/BrowserPanel.swift

Repository: manaflow-ai/cmux

Length of output: 484


🏁 Script executed:

sed -n '2860,2870p' /home/jailuser/git/Sources/Panels/BrowserPanel.swift

Repository: manaflow-ai/cmux

Length of output: 487


🏁 Script executed:

sed -n '2480,2495p' /home/jailuser/git/Sources/Panels/BrowserPanel.swift

Repository: manaflow-ai/cmux

Length of output: 797


🏁 Script executed:

grep -n "makeWebView\|Do not force page appearance" /home/jailuser/git/Sources/Panels/BrowserPanel.swift

Repository: manaflow-ai/cmux

Length of output: 394


🏁 Script executed:

sed -n '2481,2495p' /home/jailuser/git/Sources/Panels/BrowserPanel.swift

Repository: manaflow-ai/cmux

Length of output: 796


🏁 Script executed:

sed -n '185,230p' /home/jailuser/git/Sources/Panels/BrowserPanel.swift | cat -n

Repository: manaflow-ai/cmux

Length of output: 1962


🏁 Script executed:

grep -n "BrowserThemeMode\|enum BrowserThemeMode" /home/jailuser/git/Sources/Panels/BrowserPanel.swift | head -10

Repository: manaflow-ai/cmux

Length of output: 782


🏁 Script executed:

sed -n '156,165p' /home/jailuser/git/Sources/Panels/BrowserPanel.swift

Repository: manaflow-ai/cmux

Length of output: 272


🏁 Script executed:

rg "BrowserThemeMode\.(system|light|dark)" /home/jailuser/git/Sources/Panels/BrowserPanel.swift

Repository: manaflow-ai/cmux

Length of output: 42


BrowserThemeSettings.apply() still overrides webView.appearance, contradicting the "do not force page appearance" design.

The apply() method at lines 217–221 still maps .light → .aqua and .dark → .darkAqua, then reapplies that override through applyBrowserThemeModeIfNeeded() at lines 4946 and 5452 for both main and popup views. Meanwhile, the makeWebView() comment (lines 2487–2488) explicitly states "Do not force page appearance or inject color-scheme CSS; websites must keep control of their own theme" — but this intent is undermined by the active override.

Since BrowserPanel loads browserThemeMode from persisted UserDefaults (line 2607) and the migration logic (lines 206–207) preserves legacy dark-mode settings, upgraded users can remain stuck with a forced appearance indefinitely after the UI for that preference disappears.

Remove the appearance override entirely: set webView.appearance = nil unconditionally in apply(), and either clear the stale browserThemeMode preference during migration or ignore persisted values to reset to .system (the intended default).

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

In `@Sources/Panels/BrowserPanel.swift` around lines 214 - 223,
BrowserThemeSettings.apply(_:to:) currently forces webView.appearance for
.light/.dark which contradicts the "do not force page appearance" policy; change
apply(_ mode: BrowserThemeMode, to webView: WKWebView) to always set
webView.appearance = nil (remove the .aqua/.darkAqua branches) and stop calling
applyBrowserThemeModeIfNeeded(...) for both main and popup web views so no
overrides are reapplied; also update the migration/loading of browserThemeMode
(where saved UserDefaults are read) to either clear the legacy browserThemeMode
preference during migration or ignore persisted values and default to .system so
upgraded users aren’t stuck with a forced appearance.

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

2 issues found across 4 files (changes from recent commits).

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/Panels/BrowserPanelView.swift">

<violation number="1">
P1: Applying `normalizedMode` here reintroduces forced `WKWebView.appearance` overrides for light/dark modes, which can cause embedded pages to lose control of their own color scheme again.</violation>
</file>

<file name="Sources/Panels/BrowserPopupWindowController.swift">

<violation number="1" location="Sources/Panels/BrowserPopupWindowController.swift:114">
P2: This line forces popup WKWebView appearance to light/dark when a stored mode is set, which overrides embedded pages’ own color-scheme choices and can reintroduce the dark-mode bleed this PR is trying to remove. Consider leaving the appearance unset for popups as well.</violation>
</file>

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

}
webView.underPageBackgroundColor = GhosttyBackgroundTheme.currentColor()
webView.customUserAgent = BrowserUserAgentSettings.safariUserAgent
BrowserThemeSettings.apply(openerPanel?.currentBrowserThemeMode ?? BrowserThemeSettings.mode(), to: webView)

@cubic-dev-ai cubic-dev-ai Bot Mar 30, 2026 •

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2: This line forces popup WKWebView appearance to light/dark when a stored mode is set, which overrides embedded pages’ own color-scheme choices and can reintroduce the dark-mode bleed this PR is trying to remove. Consider leaving the appearance unset for popups as well.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At Sources/Panels/BrowserPopupWindowController.swift, line 114:

<comment>This line forces popup WKWebView appearance to light/dark when a stored mode is set, which overrides embedded pages’ own color-scheme choices and can reintroduce the dark-mode bleed this PR is trying to remove. Consider leaving the appearance unset for popups as well.</comment>

<file context>
@@ -111,6 +111,7 @@ final class BrowserPopupWindowController: NSObject, NSWindowDelegate {
         }
         webView.underPageBackgroundColor = GhosttyBackgroundTheme.currentColor()
         webView.customUserAgent = BrowserUserAgentSettings.safariUserAgent
+        BrowserThemeSettings.apply(openerPanel?.currentBrowserThemeMode ?? BrowserThemeSettings.mode(), to: webView)
         self.webView = webView
 
</file context>
Fix with Cubic

@austinywang

Copy link
Copy Markdown
Contributor Author

Closing this fork-based PR in favor of #2346, which is opened from manaflow-ai/cmux per repo workflow.

This branch was successfully deployed

1 active deployment
Preview — a5f760e5 Deployed Mar 30, 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.

Browser pane: text turns white after scrolling on light-mode pages (dark mode leak)

1 participant