Skip to content

mux-tui: light-theme-aware chrome adapts to host background - #7648

Merged
lawrencecchen merged 4 commits into
mainfrom
feat-mux-light-theme
Jul 10, 2026
Merged

lawrencecchen merged 4 commits into
mainfrom
feat-mux-light-theme

Conversation

@lawrencecchen

@lawrencecchen lawrencecchen commented Jul 8, 2026 •

Copy link
Copy Markdown
Contributor

In light-background terminals the mux TUI chrome (context menu, status bar, tab bar, sidebar selection, prompt, omnibar) rendered hardcoded dark ANSI indices and clashed badly — dark boxes and strips on a light terminal. The terminal content already adapts (host palette); the chrome did not.

The TUI already probes host fg/bg via OSC 10/11. The chrome now derives from it: ChromeTheme::dark() reproduces the previous hardcoded indices exactly (dark terminals render byte-identically — verified index-by-index at every converted site), ChromeTheme::light() is a curated light palette (light-gray surfaces, dark text, readable accents). Selection happens once at startup from background luminance (rec.709, >128), overridable via theme.chrome: auto|light|dark (default auto; probe failure keeps dark). Explicit config colors (tab_bg, tab_active_bg, sidebar_active_bg, borders) still take priority. Terminal content rendering and resolve_palette_color are untouched.

Tests: luminance edges, a dark-palette regression lock on known indices, auto/override selection, and config parse rejection of unknown variants.

Verified on the AWS M4 Pro builder (this Mac's macOS 26 SDK can't link ghostty-vt-sys): fmt, clippy -D warnings, cargo test --workspace (17 suites), smoke-tui all green.

🤖 Generated with Claude Code


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


Note

Low Risk
Presentation-layer theming and config precedence only; terminal pane rendering and palette resolution are unchanged, with dark mode kept index-identical via tests.

Overview
Adds host-aware TUI chrome so menus, status/tab bars, sidebar, omnibar, prompts, toasts, borders, and scrollbars no longer use fixed dark ANSI indices on light terminals.

Startup now passes probed DefaultColors into app::run, builds a ChromeTheme (dark matches prior indices; light is a new palette), and stores it on App.chrome. UI draw paths read app.chrome instead of hardcoded colors. theme.chrome (auto | light | dark, default auto) picks the palette from background luminance when not forced.

ThemeOverrides tracks explicit theme keys (selection, tab/sidebar/border colors); apply_chrome_defaults only fills selection when not overridden, including after config reload (chrome is not recomputed from the host on reload). Ghostty/mux.json selection colors still win over light chrome defaults.

Config docs and tests cover luminance, dark regression, override persistence, and invalid chrome values.

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


Summary by cubic

Make mux-tui chrome adapt to the terminal’s light or dark background, including text selection, so menus, bars, and prompts fit light terminals. Dark terminals render exactly as before; terminal content is unchanged.

  • New Features
    • Added theme.chrome with auto|light|dark (default auto); auto chooses by host background luminance at startup and falls back to dark. The result is stored as App.chrome.
    • Introduced ChromeTheme palettes: dark() matches legacy indices; light() is curated (selection #ccddf5, dark stays #3a3a3a). Applied to menu, prompt, toast, omnibar, status/tab bars, sidebar, borders, and scrollbars; terminal panes are unchanged.
    • Explicit overrides include selection, tab_bg, tab_active_bg, sidebar_active_bg, border_active, and border_inactive. Ghostty/mux.json selection colors take priority; Config::apply_chrome_defaults fills only when not overridden, and reload reapplies these defaults without recomputing the host theme.

Written for commit 793a8e1. Summary will update on new commits.

Review in cubic

Summary by CodeRabbit

  • New Features

    • Added configurable TUI “chrome” theme support with auto light/dark selection based on terminal background.
    • Introduced granular theme override tracking so explicitly set colors take precedence while chrome defaults fill gaps.
  • Bug Fixes

    • Ensured the TUI receives probed default terminal colors for chrome theming.
    • Updated chrome-driven styling across the interface (status bar, sidebar, panes/tabs, omnibar, overlays, prompts/menus, toasts, scrollbars).
    • Improved config reload so chrome-derived defaults are reapplied correctly.
  • Tests

    • Expanded coverage for auto selection, defaults/overrides behavior, and config parsing.

In light-background terminals the TUI chrome (context menu, status bar,
tab bar, sidebar selection, prompt, omnibar) rendered hardcoded dark
ANSI indices and clashed badly. The terminal already probes host fg/bg
via OSC 10/11; the chrome now uses it.

ChromeTheme::dark() reproduces the previous hardcoded indices exactly
(dark terminals render byte-identically); ChromeTheme::light() is a
curated light palette. Selection is by host-bg luminance at startup,
overridable via theme.chrome: auto|light|dark (default auto; probe
failure keeps dark). Explicit config colors (tab_bg, tab_active_bg,
sidebar_active_bg, borders) still win. Terminal content rendering and
resolve_palette_color are untouched.
@vercel

vercel Bot commented Jul 8, 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 Jul 10, 2026 8:52am
cmux-staging Building Building Preview, Comment Jul 10, 2026 8:52am

@coderabbitai

coderabbitai Bot commented Jul 8, 2026 •

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Adds chrome theme resolution and override tracking in mux-tui, threads probed default terminal colors through app startup, and switches status, omnibar, overlay, pane, and sidebar rendering to app.chrome colors.

Changes

Chrome theme system

Layer / File(s) Summary
Chrome contracts and config loading
mux/crates/mux-tui/src/config.rs
Defines ChromeMode and ChromeTheme, adds ThemeOverrides and config chrome fields, parses theme.chrome, tracks explicit theme overrides during load, and extends docs and tests for chrome resolution behavior.
App startup and reload wiring
mux/crates/mux-tui/src/app.rs, mux/crates/mux-tui/src/main.rs
Updates app::run to accept default_colors, derives and stores ChromeTheme on App, forwards probed colors from main.rs, and reapplies chrome defaults during config reloads and tests.
Status bar and omnibar styling
mux/crates/mux-tui/src/ui/mod.rs, mux/crates/mux-tui/src/ui/omnibar.rs
Replaces indexed color styling in the status bar and omnibar with app.chrome fields for base, dim, editing, hover, separator, and hidden-suffix rendering.
Overlay, pane, and sidebar styling
mux/crates/mux-tui/src/ui/overlay.rs, mux/crates/mux-tui/src/ui/pane.rs, mux/crates/mux-tui/src/ui/sidebar.rs
Updates prompt, menu, toast, pane border/tab/scrollbar/browser message, and sidebar rendering to use chrome-derived colors and theme override flags instead of hardcoded indexed palette values.

Estimated code review effort: 4 (Complex) | ~60 minutes

Possibly related PRs

  • manaflow-ai/cmux#7584: Both changes touch notification and unread-related UI styling in mux-tui, including pane rendering color selection.
  • manaflow-ai/cmux#7604: Both changes modify mux-tui reload/config flow paths that feed into app state updates.

Important

Pre-merge checks failed

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

❌ Failed checks (4 errors, 1 warning)

Check name Status Explanation Resolution
Cmux Swift Blocking Runtime ❌ Error Production Swift adds ContinuousClock().sleep debounces in AgentChatThemeSync and RemoteTmuxControlConnection+Sizing; these are timing sync, not tests. Replace the sleeps with event-driven signals/completions (theme/config change notifications, tmux size-settled callbacks) or a truly non-blocking timer abstraction.
Cmux No Hacky Sleeps ❌ Error FAIL: agent-chat/server.ts adds setTimeout/setInterval polling to resync theme when fs.watch misses replaces/missing files; that’s production sync-by-time. Replace the timer/poll fallback with an explicit filesystem/event signal or an event-driven watcher abstraction; keep fixed sleeps only in test-only scripts.
Cmux Swift Concurrency ❌ Error Diff adds internal completion-handler tmux queries and new fire-and-forget Tasks; these aren’t boundary-only async patterns. Refactor the tmux activity-query path to async/await (or a small continuation wrapper) and await or lifecycle-manage the new Tasks.
Cmux No Ambient Global State ❌ Error PR adds MobileDebugLog.shared and mutable nonisolated(unsafe) static storage, introducing process-wide singleton/global state. Move debug-log ownership into the app composition root and inject the sink/facade through callers; remove shared and static mutable storage.
Docstring Coverage ⚠️ Warning Docstring coverage is 78.95% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (20 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Cmux Swift Actor Isolation ✅ Passed No Swift actor-isolation regression found; touched code uses explicit @MainActor, actors, or documented nonisolated/@unchecked Sendable wrappers.
Cmux Browser Automation Off-Main ✅ Passed Diff only adds remote.tmux worker methods/tests; browser.wait/cookies/screenshot remain worker-routed and no browser command moved to mainActor/processV2Command.
Cmux Expensive Synchronous Load ✅ Passed PR changes are in Rust mux-tui files, not Swift, so the Swift expensive-sync-load rule doesn’t apply.
Cmux Cache Substitution Correctness ✅ Passed The changed caches are transient or freshness-checked (TTL/watchers, compare-and-set, storage-availability probes); no persistence/history/snapshot path trusts a stale cache.
Cmux Algorithmic Complexity ✅ Passed Fixed-size theme/action lookups and single-pass pane selection only; no new nested rescans or batch-scanning pattern was introduced.
Cmux Swift @Concurrent ✅ Passed No changed Swift file adds @concurrent, and the async work is either @MainActor UI-bound or actor-isolated (e.g. RemoteTmuxSSHTransport), so the rule isn’t violated.
Cmux Swift File And Package Boundaries ✅ Passed No Swift boundary violation: the only new >400-line file is a focused fork-parent fallback module; oversized connection code shrank, and the budget file wasn’t loosened.
Cmux Swiftpm Lockfiles ✅ Passed PR diff only changes mux-tui Rust sources; no SwiftPM/Xcode .gitignore, Package.swift, or Package.resolved changes are present.
Cmux Swift Logging ✅ Passed PASS: The only added logging is a DEBUG-gated NSLog in MobileDebugLog and a private nonisolated Logger; both match allowed patterns, and no forbidden app/runtime logging was added.
Cmux User-Facing Error Privacy ✅ Passed No new user-facing error copy was added; the patch is chrome/config plumbing and UI styling, while existing config-load stderr stays generic.
Cmux Full Internationalization ✅ Passed The commit only changes mux-tui Rust files; the i18n rule applies to Swift/string catalogs and web locale/message files, so it’s out of scope.
Cmux Swiftui State Layout ✅ Passed The PR only refactors existing WorkspaceListView chrome selection and adds an AppKit bridge self-sizing guard; no new forbidden SwiftUI state/layout patterns were introduced.
Cmux Architecture Rethink ✅ Passed PASS: The referenced rule is Swift-only, while this PR only changes Rust mux-tui theming; chrome ownership stays centralized in Config/App with no split lifecycle owner.
Cmux Swift Auxiliary Window Close Shortcuts ✅ Passed Only main-window/portal and view code changed; no new cmux.* auxiliary window identifiers or cmuxAuxiliaryWindowIdentifiers registrations were added.
Cmux Source Artifacts ✅ Passed Changed paths are source/config/docs or deliberate release assets; no temp/cache/log/screenshot artifacts. vendor/bonsplit is only a submodule pointer bump.
Cmux No Test Or Debug Seam In Production Source ✅ Passed The diff only touches Rust files under mux/crates/mux-tui; no production Swift Sources file was changed, so the Swift-only seam rule is not applicable.
Title check ✅ Passed The title clearly summarizes the main change: host-background-aware chrome theming for mux-tui.
Description check ✅ Passed The description covers the change, rationale, theme behavior, and testing, with only some template sections missing.
✨ 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 feat-mux-light-theme

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.

@greptile-apps

greptile-apps Bot commented Jul 8, 2026 •

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR introduces a ChromeTheme type that replaces all hardcoded dark ANSI indices in the mux TUI chrome (menus, status bar, tabs, sidebar, omnibar, borders, scrollbars) with theme-aware color lookups. The chrome is selected once at startup from the probed host background luminance and an optional theme.chrome: auto|light|dark config key; dark terminals render byte-identically to the previous code.

  • ChromeTheme::dark() mirrors the old hardcoded indices exactly; ChromeTheme::light() provides a curated light palette. for_defaults picks between them using a Rec. 709 luminance threshold (> 128) against the OSC 11 background; on probe failure it stays dark.
  • ThemeOverrides tracks which colors were explicitly set via Ghostty or mux.json; apply_chrome_defaults fills only the selection colors when no override exists, and border/tab/sidebar overrides are resolved at render time.
  • Config reload re-applies chrome defaults with the startup-computed ChromeTheme; a chrome mode change in mux.json requires a restart (intentional — reprobing OSC 10/11 would need a new async round-trip).

Confidence Score: 5/5

Safe to merge — purely presentational chrome theming with no changes to terminal content rendering, auth, or data paths; dark terminals render byte-identically to the previous code.

Every hardcoded color index substitution is mechanical and independently testable. The override priority logic (explicit Ghostty/mux.json colors win over chrome defaults) is consistently applied across all call sites and covered by the new tests. The chrome is computed once at startup from an already-probed value and stored on App, so there is no concurrency concern. The intentional limitation — chrome mode changes in mux.json require a restart — is documented in the PR and doesn't introduce incorrect behavior.

No files require special attention.

Important Files Changed

Filename Overview
mux/crates/mux-tui/src/config.rs Core of the change: adds ChromeMode, ChromeTheme (40+ color fields), ThemeOverrides, apply_chrome_defaults, and is_light_background. Override tracking is consistent across Ghostty seeds and mux.json values. Tests cover luminance edges, dark-palette regression lock, override survival, auto/manual selection, and config parse rejection.
mux/crates/mux-tui/src/app.rs Adds chrome: ChromeTheme to App, threads default_colors through run(), computes ChromeTheme once at startup, and re-applies chrome defaults on reload without recomputing from host (by design). Test helper uses ChromeTheme::dark() as baseline.
mux/crates/mux-tui/src/main.rs One-line change: passes the already-probed DefaultColors into app::run(). On probe failure, DefaultColors::default() (bg: None) is passed, which correctly falls back to dark theme.
mux/crates/mux-tui/src/ui/mod.rs Status bar colors replaced with chrome equivalents (status_bg, status_fg, status_dim_fg, status_active_bg, status_active_fg). Straightforward mechanical substitution.
mux/crates/mux-tui/src/ui/omnibar.rs Idle and editing mode colors replaced with omnibar_fg, omnibar_sep_fg, omnibar_dim_fg, omnibar_edit_bg, omnibar_edit_fg, omnibar_hover_fg. Color import removed cleanly.
mux/crates/mux-tui/src/ui/overlay.rs Prompt and menu chrome updated to use ChromeTheme fields. Toast color updated. All Color::Indexed usages removed from this file.
mux/crates/mux-tui/src/ui/pane.rs border_style refactored to take &App and check theme_overrides; tab bar, scrollbar, and browser message colors updated. Override priority logic (explicit config wins over chrome default) is correctly implemented at each call site.
mux/crates/mux-tui/src/ui/sidebar.rs Sidebar selection background resolved from theme_overrides.sidebar_active_bg (explicit config wins) or chrome.sidebar_selected_bg. Dim and border colors updated from chrome theme.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[main::run_tui] -->|probe OSC 10/11| B[DefaultColors]
    B --> C[app::run]
    C --> D[config::load]
    D --> E[Config\nchrome: ChromeMode\ntheme_overrides: ThemeOverrides]
    C --> F{ChromeTheme::for_defaults\nconfig.chrome + DefaultColors.bg}
    F -->|Auto + luminance > 128| G[ChromeTheme::light]
    F -->|Auto + luminance ≤ 128 or bg=None| H[ChromeTheme::dark]
    F -->|Light override| G
    F -->|Dark override| H
    G --> I[config.apply_chrome_defaults\nfills selection_bg/fg\nif !theme_overrides.selection]
    H --> I
    I --> J[App\nchrome: ChromeTheme\nconfig: Config]
    J -->|render| K[UI modules\nread app.chrome.*\nchecking theme_overrides at call site]
    J -->|reload_config| L[new Config loaded\napply_chrome_defaults with same chrome\nself.chrome unchanged]
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
flowchart TD
    A[main::run_tui] -->|probe OSC 10/11| B[DefaultColors]
    B --> C[app::run]
    C --> D[config::load]
    D --> E[Config\nchrome: ChromeMode\ntheme_overrides: ThemeOverrides]
    C --> F{ChromeTheme::for_defaults\nconfig.chrome + DefaultColors.bg}
    F -->|Auto + luminance > 128| G[ChromeTheme::light]
    F -->|Auto + luminance ≤ 128 or bg=None| H[ChromeTheme::dark]
    F -->|Light override| G
    F -->|Dark override| H
    G --> I[config.apply_chrome_defaults\nfills selection_bg/fg\nif !theme_overrides.selection]
    H --> I
    I --> J[App\nchrome: ChromeTheme\nconfig: Config]
    J -->|render| K[UI modules\nread app.chrome.*\nchecking theme_overrides at call site]
    J -->|reload_config| L[new Config loaded\napply_chrome_defaults with same chrome\nself.chrome unchanged]
Loading

Reviews (3): Last reviewed commit: "Merge remote-tracking branch 'origin/mai..." | Re-trigger Greptile

}
}

pub fn is_light_background(bg: Rgb) -> bool {

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 is_light_background is pub but is only used within this file — in for_defaults and the in-file test module. Rust test modules with use super::* can access non-pub items in the parent module, so no external visibility is needed. Exposing it as pub widens the crate's API surface unnecessarily; pub(crate) scopes it to the crate if other modules ever need it, or it can remain private.

Suggested change
pub fn is_light_background(bg: Rgb) -> bool {
pub(crate) fn is_light_background(bg: Rgb) -> bool {

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!

menu_bg: Color::Indexed(254),
menu_fg: Color::Indexed(236),
menu_border: Color::Indexed(246),
menu_selected_bg: Color::Indexed(252),

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 sidebar_dim_fg identical across both palettes

sidebar_dim_fg is Color::Indexed(242) (≈ #6c6c6c) in both dark() and light(). On a dark background this reads as a subtle dim, but on the light sidebar background (sidebar_selected_bg: Indexed(253) ≈ #dadada base), index 242 against a surrounding background of ~254 yields a contrast ratio below 3:1, making dim labels hard to read. A lighter index in the 238–240 range would improve legibility on light backgrounds without clashing on the dark side.

@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 current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@mux/crates/mux-tui/src/config.rs`:
- Around line 1047-1062: The regression test in
dark_chrome_matches_legacy_indices only validates a small subset of
ChromeTheme::dark() and can miss legacy-color drift. Expand the assertions in
that test to cover the remaining important fields on ChromeTheme, especially
border_active_fg plus the omitted menu_*, prompt_*, tab_plain_*, and omnibar_*
foreground/background values, so any future change to dark() breaks the legacy
parity lock.
🪄 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: d3736b75-5dae-4391-b99e-f9d55f396738

📥 Commits

Reviewing files that changed from the base of the PR and between 0302b40 and bbb21d8.

📒 Files selected for processing (8)
  • mux/crates/mux-tui/src/app.rs
  • mux/crates/mux-tui/src/config.rs
  • mux/crates/mux-tui/src/main.rs
  • mux/crates/mux-tui/src/ui/mod.rs
  • mux/crates/mux-tui/src/ui/omnibar.rs
  • mux/crates/mux-tui/src/ui/overlay.rs
  • mux/crates/mux-tui/src/ui/pane.rs
  • mux/crates/mux-tui/src/ui/sidebar.rs

Comment on lines +1047 to +1062
#[test]
fn dark_chrome_matches_legacy_indices() {
let chrome = ChromeTheme::dark();
assert_eq!(chrome.menu_bg, Color::Indexed(237));
assert_eq!(chrome.menu_selected_bg, Color::Indexed(242));
assert_eq!(chrome.prompt_bg, Color::Indexed(236));
assert_eq!(chrome.status_bg, Color::Indexed(236));
assert_eq!(chrome.status_active_bg, Color::Indexed(240));
assert_eq!(chrome.tab_bar_bg, Color::Indexed(236));
assert_eq!(chrome.tab_active_bg, Color::Indexed(240));
assert_eq!(chrome.tab_active_unfocused_bg, Color::Indexed(238));
assert_eq!(chrome.sidebar_selected_bg, Color::Indexed(236));
assert_eq!(chrome.omnibar_edit_bg, Color::Indexed(236));
assert_eq!(chrome.border_fg, Color::Indexed(238));
assert_eq!(chrome.scrollbar_thumb_active_fg, Color::Indexed(252));
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Regression-lock test only covers a quarter of ChromeTheme::dark() fields.

dark_chrome_matches_legacy_indices is meant to lock in legacy indexed-color parity, but it only asserts 12 of the struct's 44 color fields. Notably border_active_fg (Indexed(110), the focused-pane border — one of the most visually prominent colors) isn't checked, along with most menu_*, prompt_*, tab_plain_*, and omnibar_* foregrounds. A future edit to dark() could silently change legacy appearance without failing this test.

♻️ Suggested additional assertions
     #[test]
     fn dark_chrome_matches_legacy_indices() {
         let chrome = ChromeTheme::dark();
         assert_eq!(chrome.menu_bg, Color::Indexed(237));
         assert_eq!(chrome.menu_selected_bg, Color::Indexed(242));
         assert_eq!(chrome.prompt_bg, Color::Indexed(236));
+        assert_eq!(chrome.border_active_fg, Color::Indexed(110));
+        assert_eq!(chrome.omnibar_edit_fg, Color::Indexed(252));
+        assert_eq!(chrome.sidebar_border, Color::Indexed(237));
         assert_eq!(chrome.status_bg, Color::Indexed(236));
         assert_eq!(chrome.status_active_bg, Color::Indexed(240));
         assert_eq!(chrome.tab_bar_bg, Color::Indexed(236));
📝 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.

Suggested change
#[test]
fn dark_chrome_matches_legacy_indices() {
let chrome = ChromeTheme::dark();
assert_eq!(chrome.menu_bg, Color::Indexed(237));
assert_eq!(chrome.menu_selected_bg, Color::Indexed(242));
assert_eq!(chrome.prompt_bg, Color::Indexed(236));
assert_eq!(chrome.status_bg, Color::Indexed(236));
assert_eq!(chrome.status_active_bg, Color::Indexed(240));
assert_eq!(chrome.tab_bar_bg, Color::Indexed(236));
assert_eq!(chrome.tab_active_bg, Color::Indexed(240));
assert_eq!(chrome.tab_active_unfocused_bg, Color::Indexed(238));
assert_eq!(chrome.sidebar_selected_bg, Color::Indexed(236));
assert_eq!(chrome.omnibar_edit_bg, Color::Indexed(236));
assert_eq!(chrome.border_fg, Color::Indexed(238));
assert_eq!(chrome.scrollbar_thumb_active_fg, Color::Indexed(252));
}
#[test]
fn dark_chrome_matches_legacy_indices() {
let chrome = ChromeTheme::dark();
assert_eq!(chrome.menu_bg, Color::Indexed(237));
assert_eq!(chrome.menu_selected_bg, Color::Indexed(242));
assert_eq!(chrome.prompt_bg, Color::Indexed(236));
assert_eq!(chrome.border_active_fg, Color::Indexed(110));
assert_eq!(chrome.omnibar_edit_fg, Color::Indexed(252));
assert_eq!(chrome.sidebar_border, Color::Indexed(237));
assert_eq!(chrome.status_bg, Color::Indexed(236));
assert_eq!(chrome.status_active_bg, Color::Indexed(240));
assert_eq!(chrome.tab_bar_bg, Color::Indexed(236));
assert_eq!(chrome.tab_active_bg, Color::Indexed(240));
assert_eq!(chrome.tab_active_unfocused_bg, Color::Indexed(238));
assert_eq!(chrome.sidebar_selected_bg, Color::Indexed(236));
assert_eq!(chrome.omnibar_edit_bg, Color::Indexed(236));
assert_eq!(chrome.border_fg, Color::Indexed(238));
assert_eq!(chrome.scrollbar_thumb_active_fg, Color::Indexed(252));
}
🤖 Prompt for 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.

In `@mux/crates/mux-tui/src/config.rs` around lines 1047 - 1062, The regression
test in dark_chrome_matches_legacy_indices only validates a small subset of
ChromeTheme::dark() and can miss legacy-color drift. Expand the assertions in
that test to cover the remaining important fields on ChromeTheme, especially
border_active_fg plus the omitted menu_*, prompt_*, tab_plain_*, and omnibar_*
foreground/background values, so any future change to dark() breaks the legacy
parity lock.

Selection joins ChromeTheme: dark keeps the existing 0x3a3a3a default,
light substitutes a macOS-style light blue (#ccddf5). Explicit mux.json
selection colors and Ghostty-seeded selection colors still win via a
theme_overrides.selection origin flag; the substitution applies at
startup and on config reload.
Comment thread cmux-tui/crates/cmux-tui/src/app.rs
Comment thread cmux-tui/crates/cmux-tui/src/config.rs

@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 `@mux/crates/mux-tui/src/app.rs`:
- Around line 851-856: The reload_config flow currently reapplies the new config
but keeps using the startup chrome value, so changes to theme.chrome are ignored
until restart. Update App to retain the probed DefaultColors and, inside
reload_config, rebuild self.chrome with ChromeTheme::for_defaults before calling
apply_chrome_defaults and session.apply_config. Use the existing reload_config
and ChromeTheme::for_defaults symbols to locate the change, and ensure
self.chrome is refreshed from the reloaded config rather than reused from
startup.

In `@mux/crates/mux-tui/src/config.rs`:
- Around line 816-823: The method name is too broad for what it actually does:
Config::apply_chrome_defaults only updates selection_bg and selection_fg. Rename
it to something more specific like apply_chrome_selection_defaults, and update
any call sites or references so the narrow contract is clear to future readers.
🪄 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: 2776eeae-adf2-45dd-a47b-5734971a2196

📥 Commits

Reviewing files that changed from the base of the PR and between bbb21d8 and e53ce43.

📒 Files selected for processing (2)
  • mux/crates/mux-tui/src/app.rs
  • mux/crates/mux-tui/src/config.rs

Comment on lines 851 to 856
fn reload_config(&mut self) {
let config = crate::config::load();
let mut config = crate::config::load();
config.apply_chrome_defaults(self.chrome);
self.session.apply_config(&config);
self.config = config;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Map relevant Rust files and inspect the app/state/theme flow.
git ls-files 'mux/crates/mux-tui/src/*.rs' | sed -n '1,120p'

printf '\n--- app.rs outline ---\n'
ast-grep outline mux/crates/mux-tui/src/app.rs --view expanded || true

printf '\n--- search for chrome/default colors/app state ---\n'
rg -n "default_colors|ChromeTheme::for_defaults|apply_chrome_defaults|reload_config|theme\\.chrome|chrome" mux/crates/mux-tui/src -S

Repository: manaflow-ai/cmux

Length of output: 24411


🏁 Script executed:

#!/bin/bash
set -euo pipefail

sed -n '540,665p' mux/crates/mux-tui/src/app.rs
printf '\n--- reload_config and nearby ---\n'
sed -n '843,865p' mux/crates/mux-tui/src/app.rs
printf '\n--- ChromeTheme / apply_chrome_defaults / for_defaults ---\n'
sed -n '780,840p' mux/crates/mux-tui/src/config.rs

Repository: manaflow-ai/cmux

Length of output: 7059


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect ChromeTheme definitions and any runtime source of default colors.
rg -n "struct ChromeTheme|enum ChromeMode|impl ChromeTheme|for_defaults|DefaultColors|probe_default_colors|set_default_colors" mux/crates/mux-tui/src -S

printf '\n--- host_colors.rs ---\n'
sed -n '1,140p' mux/crates/mux-tui/src/host_colors.rs

printf '\n--- relevant config.rs section ---\n'
sed -n '1,220p' mux/crates/mux-tui/src/config.rs

Repository: manaflow-ai/cmux

Length of output: 15093


Recompute chrome on config reload reload_config reloads config, but self.chrome stays at the startup value, so changing theme.chrome in mux.json has no effect until restart. Store the probed DefaultColors on App and rebuild self.chrome with ChromeTheme::for_defaults(config.chrome, default_colors) before apply_chrome_defaults.

🤖 Prompt for 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.

In `@mux/crates/mux-tui/src/app.rs` around lines 851 - 856, The reload_config flow
currently reapplies the new config but keeps using the startup chrome value, so
changes to theme.chrome are ignored until restart. Update App to retain the
probed DefaultColors and, inside reload_config, rebuild self.chrome with
ChromeTheme::for_defaults before calling apply_chrome_defaults and
session.apply_config. Use the existing reload_config and
ChromeTheme::for_defaults symbols to locate the change, and ensure self.chrome
is refreshed from the reloaded config rather than reused from startup.

Comment on lines +816 to +823
impl Config {
pub fn apply_chrome_defaults(&mut self, chrome: ChromeTheme) {
if !self.theme_overrides.selection {
self.theme.selection_bg = chrome.selection_bg;
self.theme.selection_fg = chrome.selection_fg;
}
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

apply_chrome_defaults is narrowly scoped to selection only — consider a more specific name.

The method name suggests it applies all chrome-derived defaults, but it only ever touches selection_bg/selection_fg. Given the rest of the chrome palette is consumed directly from app.chrome at render time (not config.theme), this is likely intentional, but a name like apply_chrome_selection_defaults would make the narrow contract obvious to future readers touching this method.

🤖 Prompt for 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.

In `@mux/crates/mux-tui/src/config.rs` around lines 816 - 823, The method name is
too broad for what it actually does: Config::apply_chrome_defaults only updates
selection_bg and selection_fg. Rename it to something more specific like
apply_chrome_selection_defaults, and update any call sites or references so the
narrow contract is clear to future readers.

# Conflicts:
#	mux/crates/mux-tui/src/config.rs
#	mux/crates/mux-tui/src/ui/omnibar.rs
#	mux/crates/mux-tui/src/ui/overlay.rs
# Conflicts:
#	cmux-tui/crates/cmux-tui/src/config.rs
@greptile-apps

greptile-apps Bot commented Jul 10, 2026

Copy link
Copy Markdown
Contributor

Too many files changed for review. (967 files found, 100 file limit)

@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 using default effort 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 793a8e1. Configure here.

@@ -0,0 +1 @@
{"rustc_fingerprint":7916351246056945259,"outputs":{"17747080675513052775":{"success":true,"status":"","code":0,"stdout":"rustc 1.94.0 (4a4ef493e 2026-03-02)\nbinary: rustc\ncommit-hash: 4a4ef493e3a1488c6e321570238084b38948f6db\ncommit-date: 2026-03-02\nhost: aarch64-apple-darwin\nrelease: 1.94.0\nLLVM version: 21.1.8\n","stderr":""},"7971740275564407648":{"success":true,"status":"","code":0,"stdout":"___\nlib___.rlib\nlib___.dylib\nlib___.dylib\nlib___.a\nlib___.dylib\n/Users/lawrence/.rustup/toolchains/stable-aarch64-apple-darwin\noff\npacked\nunpacked\n___\ndebug_assertions\npanic=\"unwind\"\nproc_macro\ntarget_abi=\"\"\ntarget_arch=\"aarch64\"\ntarget_endian=\"little\"\ntarget_env=\"\"\ntarget_family=\"unix\"\ntarget_feature=\"aes\"\ntarget_feature=\"crc\"\ntarget_feature=\"dit\"\ntarget_feature=\"dotprod\"\ntarget_feature=\"dpb\"\ntarget_feature=\"dpb2\"\ntarget_feature=\"fcma\"\ntarget_feature=\"fhm\"\ntarget_feature=\"flagm\"\ntarget_feature=\"fp16\"\ntarget_feature=\"frintts\"\ntarget_feature=\"jsconv\"\ntarget_feature=\"lor\"\ntarget_feature=\"lse\"\ntarget_feature=\"neon\"\ntarget_feature=\"paca\"\ntarget_feature=\"pacg\"\ntarget_feature=\"pan\"\ntarget_feature=\"pmuv3\"\ntarget_feature=\"ras\"\ntarget_feature=\"rcpc\"\ntarget_feature=\"rcpc2\"\ntarget_feature=\"rdm\"\ntarget_feature=\"sb\"\ntarget_feature=\"sha2\"\ntarget_feature=\"sha3\"\ntarget_feature=\"ssbs\"\ntarget_feature=\"vh\"\ntarget_has_atomic=\"128\"\ntarget_has_atomic=\"16\"\ntarget_has_atomic=\"32\"\ntarget_has_atomic=\"64\"\ntarget_has_atomic=\"8\"\ntarget_has_atomic=\"ptr\"\ntarget_os=\"macos\"\ntarget_pointer_width=\"64\"\ntarget_vendor=\"apple\"\nunix\n","stderr":""}},"successes":{}} No newline at end of file

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Committed Cargo build artifacts

Medium Severity

The diff adds the entire mux/target/ tree (fingerprints, debug/deps .d files, CACHEDIR.TAG, and local machine paths). That is generated Cargo output, not source for the chrome theme work, and it bloats the repo and can confuse other builds.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 793a8e1. Configure here.

@lawrencecchen
lawrencecchen merged commit f38b303 into main Jul 10, 2026
47 of 53 checks passed

This branch was successfully deployed

1 active deployment
Preview – cmux — 793a8e1e Deployed Jul 10, 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