Skip to content

Add Appearance settings tab - #7000

Closed
austinywang wants to merge 28 commits into
mainfrom
issue-5811-add-an-appearance-settings-tab-like-warp-term
Closed

austinywang wants to merge 28 commits into
mainfrom
issue-5811-add-an-appearance-settings-tab-like-warp-term

Conversation

@austinywang

@austinywang austinywang commented Jun 26, 2026 •

Copy link
Copy Markdown
Contributor

Fixes #5811

Summary

  • Add an Appearance settings section that consolidates app appearance, terminal font, UI font sizing, sidebar background matching, and workspace color controls.
  • Add terminal font family/size settings backed by the editable Ghostty config and live reload.
  • Move settings search anchors for appearance-related rows into the new section and cover the new mappings in tests.
  • Address review-bot pre-merge checks by marking the settings logger nonisolated and completing Appearance string catalog entries for every supported locale.

Testing

  • swift test in Packages/macOS/CmuxFoundation
  • swift test in Packages/macOS/CmuxSettingsUI
  • git diff --check
  • python3 scripts/check-package-resolved-policy.py
  • python3 scripts/swift_file_length_budget.py --budget .github/swift-file-length-budget.tsv
  • python3 -m json.tool Resources/Localizable.xcstrings >/dev/null
  • Appearance localization audit: verified 17 new Appearance keys across all 20 Resources/Localizable.xcstrings locales

No dev app build, reload script, or bare xcodebuild was run per issue instructions.

Demo Video

For UI or behavior changes, include a short demo video (GitHub upload, Loom, or other direct link).

  • Video URL or attachment: Not recorded in this automation run because dev app builds and launches are explicitly prohibited for this PR.

Review Trigger (Copy/Paste as PR comment)

@codex review
@coderabbitai review
@greptile-apps review
@cubic-dev-ai review

Checklist

  • I tested the change locally with package tests and structured validation
  • I added or updated tests for behavior changes
  • I updated docs/changelog if needed; no docs/changelog entry was needed for this settings-only change
  • I requested bot reviews after my latest commit; automatic PR review/check integrations are running on the latest push
  • All code review bot comments are resolved
  • All human review comments are resolved

Summary by CodeRabbit

  • New Features

    • Added a dedicated Appearance settings area for theme/app appearance, terminal font family/size, and related workspace color options, with updated placement and search discovery.
    • Exposed terminal font family/size getters and async setters that persist changes and reload live configuration.
  • Bug Fixes

    • Improved Ghostty config parsing and saving: correct font-family reset/empty behavior, clamping (including non-finite values), and normalized config updates while preserving fallback font-family entries.
  • Tests

    • Added coverage for font parsing, clamping, round-trip config writes, and Appearance search/anchor mapping.
  • Localization

    • Added/updated English and Japanese strings and search aliases for Appearance and terminal font settings.

@vercel

vercel Bot commented Jun 26, 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 5, 2026 2:44am
cmux-staging Building Building Preview, Comment Jul 5, 2026 2:44am

@coderabbitai

coderabbitai Bot commented Jun 26, 2026 •

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds an Appearance settings section that centralizes terminal font, UI sizing, theme, sidebar matching, and workspace color controls. It also extends Ghostty config parsing/writing, updates navigation/search metadata, and removes the migrated controls from older settings sections.

Changes

Appearance settings

Layer / File(s) Summary
Terminal font plumbing
Packages/macOS/CmuxFoundation/.../CmuxGhosttyConfigSettingEditor.swift, Sources/HostSettingsActions.swift, Packages/macOS/CmuxSettingsUI/Sources/CmuxSettingsUI/Environment/..., Packages/macOS/CmuxFoundation/Tests/.../TerminalFontConfigEditorTests.swift, Sources/Settings/ConfigSource.swift, Packages/macOS/CmuxTerminalCore/.../GhosttyConfig.swift, Packages/macOS/CmuxTerminalCore/Tests/.../GhosttyConfigFontFamilyTests.swift
Adds terminal font keys, parsing/clamping helpers, host read/write APIs, persistence reloads, config-editor tests, and config parsing for the effective terminal family.
Appearance navigation and search
Sources/SettingsNavigation.swift, Sources/SettingsSearchAliases.swift, Packages/macOS/CmuxSettingsUI/Sources/CmuxSettingsUI/Navigation/..., Resources/Localizable.xcstrings, Packages/macOS/CmuxSettingsUI/Tests/CmuxSettingsUITests/..., cmuxTests/SettingsSearchIndexTests.swift
Adds the .appearance section and target, remaps appearance-related rows and aliases, and updates localized labels plus search/anchor tests.
AppearanceSection view
Packages/macOS/CmuxSettingsUI/Sources/CmuxSettingsUI/Sections/AppearanceSection.swift, Packages/macOS/CmuxSettingsUI/Sources/CmuxSettingsUI/Sections/MonospacedFontFamilyCatalog.swift
Builds the new settings view with theme, terminal font, global font magnification, sidebar matching, and workspace color controls, plus monospaced font-family discovery.
Window wiring and legacy sections
Packages/macOS/CmuxSettingsUI/Sources/CmuxSettingsUI/Scene/SettingsWindowScene.swift, Packages/macOS/CmuxSettingsUI/Sources/CmuxSettingsUI/Sections/AppSection.swift, Packages/macOS/CmuxSettingsUI/Sources/CmuxSettingsUI/Sections/SidebarSection.swift, Packages/macOS/CmuxSettingsUI/Sources/CmuxSettingsUI/Sections/TerminalSection.swift, Packages/macOS/CmuxSettingsUI/Sources/CmuxSettingsUI/Sections/WorkspaceColorsSection.swift
Inserts AppearanceSection into the settings stack and removes the migrated appearance, sidebar, terminal, and workspace color controls from existing sections.

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

Sequence Diagram(s)

sequenceDiagram
  participant AppearanceSection
  participant HostSettingsActions
  participant CmuxGhosttyConfigSettingEditor
  participant GhosttyApp
  AppearanceSection->>HostSettingsActions: setTerminalFontFamily(...) / setTerminalFontSize(...)
  HostSettingsActions->>CmuxGhosttyConfigSettingEditor: writeEditableSetting(...)
  HostSettingsActions->>GhosttyApp: reloadConfiguration(reloadSource)
Loading

Important

Pre-merge checks failed

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

❌ Failed checks (2 errors, 1 warning)

Check name Status Explanation Resolution
Cmux Swift File And Package Boundaries ❌ Error New 564-line AppearanceSection mixes UI, @State, async font loading, and persistence/save tasks, violating the clear-single-responsibility boundary. Split terminal-font, color, and font-size persistence into smaller helpers/view models or package services; keep AppearanceSection mostly declarative UI.
Cmux Full Internationalization ❌ Error New Appearance UI strings are localized in code, but several touched xcstrings keys only have en/ja (or fewer) of the 20 supported locales. Add translations in Resources/Localizable.xcstrings for every supported locale for the new Appearance-related keys, especially fontSize, sidebar/tab-bar, and workspace color strings.
Docstring Coverage ⚠️ Warning Docstring coverage is 13.46% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (22 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly matches the main change: adding a new Appearance settings tab.
Description check ✅ Passed The description follows the template with Summary, Testing, Demo Video, Review Trigger, and Checklist sections filled out.
Linked Issues check ✅ Passed The changes implement the requested Appearance tab and related terminal, sidebar, workspace, and theme settings, with tests and anchors updated.
Out of Scope Changes check ✅ Passed The diff appears focused on the Appearance tab work, localization, navigation, tests, and config plumbing needed for it.
Cmux Swift Actor Isolation ✅ Passed New settings APIs and UI are explicitly @MainActor, writes are offloaded to FontConfigWriter actor, and no new background access to UI-bound stores was found.
Cmux Swift Blocking Runtime ✅ Passed Commit only adds a test file with no waits, sleeps, syncs, semaphores, or locks in production code.
Cmux Browser Automation Off-Main ✅ Passed PR diff only adds a terminal-font test; no browser.* routing, worker-policy, or main-actor browser automation code changed.
Cmux Expensive Synchronous Load ✅ Passed No touched file adds/moves agent-history loads onto @MainActor; the new font-family scan is off-main via Task.detached, and sync Ghostty config reads are config-cache paths.
Cmux Cache Substitution Correctness ✅ Passed GhosttyConfig.load() fresh-loads on cold cache, and reloadConfiguration invalidates it after terminal-font writes, so the UI snapshots aren’t arbitrarily stale.
Cmux No Hacky Sleeps ✅ Passed Only changed file is a Swift test; no non-Swift runtime changes or sleep/delay/polling patterns were present.
Cmux Algorithmic Complexity ✅ Passed New scans are one-time font-family enumeration and linear config-line edits; no nested rescans on scalable user collections or hot-path repeated sorts.
Cmux Swift Concurrency ✅ Passed No forbidden legacy async patterns were introduced; new Tasks are SwiftUI-tied or stored/cancelled, and the lone KVO hop is an allowed OS boundary.
Cmux Swift @Concurrent ✅ Passed New async work either stays @MainActor or hops explicitly via Task.detached/FontConfigWriter; no missing or invalid @concurrent use found.
Cmux Swiftpm Lockfiles ✅ Passed PR diff touches no Package.resolved, Package.swift, project.pbxproj, or cmux-owned .gitignore files, so no SwiftPM lockfile rule is violated.
Cmux Swift Logging ✅ Passed Only production logging change is hostSettingsLogger in Sources/HostSettingsActions.swift, now nonisolated private let with Logger.warning; no print/NSLog or sensitive-data logging.
Cmux User-Facing Error Privacy ✅ Passed New save-failure copy is generic and user-facing strings omit vendor/internal details; only internal logging uses the setting key.
Cmux Swiftui State Layout ✅ Passed No new ObservableObject/@published, GeometryReader expansion, or lazy-row store refs were introduced; the new Appearance section uses @State + value snapshots and lifecycle tasks.
Cmux Architecture Rethink ✅ Passed AppearanceSection follows established observation/save patterns; no sleeps, polling, duplicate owners, or split UI lifecycle were introduced.
Cmux Swift Auxiliary Window Close Shortcuts ✅ Passed PR only reshapes the existing cmux.settings settings WindowGroup; no new NSWindow/WindowGroup or identifier changes were added, and cmux.settings is already owned.
Cmux Source Artifacts ✅ Passed All changed paths are intentional source/config/test/localization files; the lone .github TSV is a checked-in test-system budget, and no artifact/scratch paths appear.
Cmux No Test Or Debug Seam In Production Source ✅ Passed No new test/debug seams in production Sources: the new font APIs are feature-facing and have production callers in AppearanceSection and ConfigSource, with no seam-like names added.
Cmux No Ambient Global State ✅ Passed No new file-scope funcs/vars or singleton state found; added helpers live on struct/protocol/actor types, and the new catalog is instantiable, not a static namespace.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch issue-5811-add-an-appearance-settings-tab-like-warp-term

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.

cmux added 2 commits June 26, 2026 16:48
…earance-settings-tab-like-warp-term

# Conflicts:
#	.github/swift-file-length-budget.tsv
@greptile-apps

greptile-apps Bot commented Jun 26, 2026 •

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR introduces a dedicated Appearance settings tab that consolidates app theme, terminal font family/size, UI font scale, sidebar background, and workspace color controls from several disparate sections into one pane, backed by new Ghostty config read/write logic and live reload.

  • Terminal font controls: CmuxGhosttyConfigSettingEditor gains updatedTerminalFontFamilyContents and writeTerminalFontFamily, which preserve secondary font-family fallback entries (for CJK fonts, etc.) when the primary is changed — fixing the silent data-loss bug in the previous updatedContents path.
  • GhosttyConfig parsing fix: split(omittingEmptySubsequences: false) and hasTerminalFontFamilyPrimary tracking correctly handle the font-family = reset directive and multi-entry fallback chains, and survive nested config-file includes by restoring the outer config's primary after each recursive parse.
  • Off-main font catalog: MonospacedFontFamilyCatalog is loaded via Task.detached in a .task modifier, correctly addressing the previous main-thread scan concern; AppearanceSection seeds the picker with an empty array and populates it asynchronously.

Confidence Score: 5/5

Safe to merge. The previously reported compile errors and fallback font-family data-loss path are both resolved in the current HEAD.

All three issues flagged in prior review rounds — dangling identifiers in AppSection, missing updatedTerminalFontFamilyContents, and silent overwrite of fallback font-family chains — are addressed and covered by new tests. The GhosttyConfig parsing changes are correct and localised. The one remaining concern runs off the main actor and does not affect correctness.

MonospacedFontFamilyCatalog.swift — the per-family CoreText scan is worth revisiting for caching if users on font-heavy machines report a slow first open of the Appearance pane.

Important Files Changed

Filename Overview
Packages/macOS/CmuxFoundation/Sources/CmuxFoundation/ConfigPaths/CmuxGhosttyConfigSettingEditor.swift Adds font-family/size getters, clamping, updatedTerminalFontFamilyContents (preserving fallback chains), writeTerminalFontFamily, and a configLines/serializedConfigLines refactor. The primaryTerminalFontFamilyIndex logic correctly identifies the first non-empty entry after the last reset.
Packages/macOS/CmuxSettingsUI/Sources/CmuxSettingsUI/Sections/AppearanceSection.swift New 564-line section consolidating theme, terminal font, UI scale, sidebar background, and workspace color rows. Font families are loaded off-main via Task.detached in .task, correctly addressing the previous main-thread scan concern.
Packages/macOS/CmuxSettingsUI/Sources/CmuxSettingsUI/Sections/AppSection.swift Removes appearance and globalFontMagnification state and their references in startSettingsObservation. The dangling-identifier compile error from the previous review is resolved.
Packages/macOS/CmuxTerminalCore/Sources/CmuxTerminalCore/Config/GhosttyConfig.swift Adds parsedTerminalFontFamily/hasTerminalFontFamilyPrimary locals to correctly track the primary font and restore it after recursive config-file includes. The omittingEmptySubsequences: false change correctly enables empty-value (reset) detection for font-family =.
Packages/macOS/CmuxSettingsUI/Sources/CmuxSettingsUI/Sections/MonospacedFontFamilyCatalog.swift New struct enumerating monospaced font families via CoreText off-main. O(n × m) per-family query could be slow on font-heavy machines; a result cache would improve repeated opens.
Sources/HostSettingsActions.swift Adds terminalFontFamily(), setTerminalFontFamily(_:), terminalFontSize(), and setTerminalFontSize(_:) backed by the cached GhosttyConfig; routes font-family writes through writeEditableSetting to preserve fallback chains. Logger correctly marked nonisolated.
Packages/macOS/CmuxSettingsUI/Sources/CmuxSettingsUI/Sections/WorkspaceColorsSection.swift Strips indicator-style, selection-color, and badge-color rows (moved to AppearanceSection) and simplifies the init signature. Remaining palette editor and Reset Palette action are intact.
Packages/macOS/CmuxFoundation/Tests/CmuxFoundationTests/TerminalFontConfigEditorTests.swift New test suite covering font family parse (primary vs. fallback), size clamping, round-trip write, and fallback preservation across all correctness-critical paths.

Sequence Diagram

%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
    participant UI as AppearanceSection (@MainActor)
    participant HA as HostSettingsActions
    participant FCW as FontConfigWriter (actor)
    participant CE as CmuxGhosttyConfigSettingEditor
    participant GC as GhosttyConfig (cached)

    UI->>HA: terminalFontFamily() [sync, init]
    HA->>GC: load().fontFamily
    GC-->>HA: "Menlo" (cached)
    HA-->>UI: "Menlo"

    UI->>UI: Task.detached → MonospacedFontFamilyCatalog().families()
    UI-->>UI: terminalFontFamilies populated (off-main)

    UI->>HA: setTerminalFontFamily("SF Mono") [async]
    HA->>FCW: write(key: "font-family", value: "SF Mono")
    FCW->>CE: writeEditableSetting(key:value:to:)
    CE->>CE: updatedTerminalFontFamilyContents (preserve fallbacks)
    CE-->>FCW: updated config string
    FCW-->>HA: true
    HA->>HA: reload open windows
    HA-->>UI: true
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"}}}%%
sequenceDiagram
    participant UI as AppearanceSection (@MainActor)
    participant HA as HostSettingsActions
    participant FCW as FontConfigWriter (actor)
    participant CE as CmuxGhosttyConfigSettingEditor
    participant GC as GhosttyConfig (cached)

    UI->>HA: terminalFontFamily() [sync, init]
    HA->>GC: load().fontFamily
    GC-->>HA: "Menlo" (cached)
    HA-->>UI: "Menlo"

    UI->>UI: Task.detached → MonospacedFontFamilyCatalog().families()
    UI-->>UI: terminalFontFamilies populated (off-main)

    UI->>HA: setTerminalFontFamily("SF Mono") [async]
    HA->>FCW: write(key: "font-family", value: "SF Mono")
    FCW->>CE: writeEditableSetting(key:value:to:)
    CE->>CE: updatedTerminalFontFamilyContents (preserve fallbacks)
    CE-->>FCW: updated config string
    FCW-->>HA: true
    HA->>HA: reload open windows
    HA-->>UI: true
Loading

Reviews (21): Last reviewed commit: "Avoid constructing fonts during monospac..." | 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.

Actionable comments posted: 2

Caution

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

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

478-483: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Add the missing app.globalFontMagnification anchor mapping.

The new .appearance row exists, and the package-side curated entry already declares paths: ["app.globalFontMagnification"], but this dictionary never maps that path. In the app target, anchorID(forSettingsPath: "app.globalFontMagnification") will return nil, so deep-link/highlight navigation for that row breaks.

🩹 Suggested fix
         "app.language": settingID(for: .app, idSuffix: "language"),
         "app.appearance": settingID(for: .appearance, idSuffix: "appearance"),
+        "app.globalFontMagnification": settingID(for: .appearance, idSuffix: "global-font-magnification"),
         "app.appIcon": settingID(for: .app, idSuffix: "app-icon"),
🤖 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 `@Sources/SettingsNavigation.swift` around lines 478 - 483, The settings anchor
map in SettingsNavigation.settingsPathAnchorIDs is missing the
app.globalFontMagnification entry, so anchorID(forSettingsPath:) cannot resolve
that settings row. Add a mapping for "app.globalFontMagnification" using the
same settingID(for:idSuffix:) pattern as the other app anchors, and make sure it
points to the .appearance section so deep-link and highlight navigation works
for the new row.
🤖 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
`@Packages/macOS/CmuxSettingsUI/Sources/CmuxSettingsUI/Sections/AppearanceSection.swift`:
- Around line 120-157: The font save helpers in AppearanceSection are only
canceling the wrapper Task, so in-flight hostActions.set… writes can still
complete out of order and overwrite newer values. Update saveTerminalFontFamily,
saveTerminalFontSize, saveSidebarFontSize, and saveSurfaceTabBarFontSize to use
a single serialized/coalescing save path so only the latest font-setting change
is persisted. Keep the existing cancel logic only if it truly prevents the
underlying write; otherwise ensure the actual save operation is queued or
awaited sequentially rather than overlapping.
- Around line 247-255: The Appearance rows for app.globalFontMagnification,
sidebarAppearance.matchTerminalBackground, and workspaceColors.* are missing
matching search anchors even though
SettingsSearchIndex.anchorID(forSettingsPath:) now points to them. Update the
affected SettingsCardRow declarations in AppearanceSection so each row publishes
the corresponding searchAnchorID (for the setting:appearance:* anchors) and keep
the anchor mapping aligned with the path-to-anchor logic used by
SettingsSearchIndex.

---

Outside diff comments:
In `@Sources/SettingsNavigation.swift`:
- Around line 478-483: The settings anchor map in
SettingsNavigation.settingsPathAnchorIDs is missing the
app.globalFontMagnification entry, so anchorID(forSettingsPath:) cannot resolve
that settings row. Add a mapping for "app.globalFontMagnification" using the
same settingID(for:idSuffix:) pattern as the other app anchors, and make sure it
points to the .appearance section so deep-link and highlight navigation works
for the new row.
🪄 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: 9b54cd8f-2f3b-4c04-917c-fb284aad8946

📥 Commits

Reviewing files that changed from the base of the PR and between 58830db and 1c39830.

⛔ Files ignored due to path filters (1)
  • .github/swift-file-length-budget.tsv is excluded by !**/*.tsv
📒 Files selected for processing (18)
  • Packages/macOS/CmuxFoundation/Sources/CmuxFoundation/ConfigPaths/CmuxGhosttyConfigSettingEditor.swift
  • Packages/macOS/CmuxFoundation/Tests/CmuxFoundationTests/TerminalFontConfigEditorTests.swift
  • Packages/macOS/CmuxSettingsUI/Sources/CmuxSettingsUI/Environment/SettingsHostActions.swift
  • Packages/macOS/CmuxSettingsUI/Sources/CmuxSettingsUI/Navigation/CuratedSettingEntry+Default.swift
  • Packages/macOS/CmuxSettingsUI/Sources/CmuxSettingsUI/Navigation/SettingsSectionID.swift
  • Packages/macOS/CmuxSettingsUI/Sources/CmuxSettingsUI/Scene/SettingsWindowScene.swift
  • Packages/macOS/CmuxSettingsUI/Sources/CmuxSettingsUI/Sections/AppSection.swift
  • Packages/macOS/CmuxSettingsUI/Sources/CmuxSettingsUI/Sections/AppearanceSection.swift
  • Packages/macOS/CmuxSettingsUI/Sources/CmuxSettingsUI/Sections/SidebarSection.swift
  • Packages/macOS/CmuxSettingsUI/Sources/CmuxSettingsUI/Sections/TerminalSection.swift
  • Packages/macOS/CmuxSettingsUI/Sources/CmuxSettingsUI/Sections/WorkspaceColorsSection.swift
  • Packages/macOS/CmuxSettingsUI/Tests/CmuxSettingsUITests/SettingsRowAnchorResolutionTests.swift
  • Packages/macOS/CmuxSettingsUI/Tests/CmuxSettingsUITests/SettingsSearchIndexTests.swift
  • Resources/Localizable.xcstrings
  • Sources/HostSettingsActions.swift
  • Sources/SettingsNavigation.swift
  • Sources/SettingsSearchAliases.swift
  • cmuxTests/SettingsSearchIndexTests.swift
💤 Files with no reviewable changes (1)
  • Packages/macOS/CmuxSettingsUI/Sources/CmuxSettingsUI/Sections/TerminalSection.swift

cmux added 5 commits June 28, 2026 20:26
…earance-settings-tab-like-warp-term

# Conflicts:
#	.github/swift-file-length-budget.tsv
#	Resources/Localizable.xcstrings
…earance-settings-tab-like-warp-term

# Conflicts:
#	Packages/macOS/CmuxSettingsUI/Sources/CmuxSettingsUI/Sections/WorkspaceColorsSection.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.

Caution

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

⚠️ Outside diff range comments (1)
Packages/macOS/CmuxFoundation/Sources/CmuxFoundation/ConfigPaths/CmuxGhosttyConfigSettingEditor.swift (1)

188-240: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Duplicate read/write boilerplate between writeSetting and writeTerminalFontFamily.

Both methods perform the identical resolve-write-URL → read-existing-contents → create-directory → write-back sequence, differing only in which updated*Contents transform is applied. Consider extracting a shared private helper that takes the content-transform closure.

♻️ Proposed refactor: extract shared write helper
+    private func writeTransformedContents(
+        to url: URL,
+        fileManager: FileManager,
+        transform: (String) -> String
+    ) throws {
+        let writeURL = configWriteURL(for: url, fileManager: fileManager)
+        let contents = (try? String(contentsOf: writeURL, encoding: .utf8))
+            ?? (try? String(contentsOf: url, encoding: .utf8))
+            ?? ""
+        let updated = transform(contents)
+        try fileManager.createDirectory(
+            at: writeURL.deletingLastPathComponent(),
+            withIntermediateDirectories: true,
+            attributes: nil
+        )
+        try updated.write(to: writeURL, atomically: true, encoding: .utf8)
+    }
+
     public func writeSetting(
         key: String,
         value: String,
         to url: URL,
         fileManager: FileManager = .default
     ) throws {
-        let writeURL = configWriteURL(for: url, fileManager: fileManager)
-        let contents = (try? String(contentsOf: writeURL, encoding: .utf8))
-            ?? (try? String(contentsOf: url, encoding: .utf8))
-            ?? ""
-        let updated = updatedContents(contents, setting: key, value: value)
-        try fileManager.createDirectory(
-            at: writeURL.deletingLastPathComponent(),
-            withIntermediateDirectories: true,
-            attributes: nil
-        )
-        try updated.write(to: writeURL, atomically: true, encoding: .utf8)
+        try writeTransformedContents(to: url, fileManager: fileManager) {
+            updatedContents($0, setting: key, value: value)
+        }
     }
🤖 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
`@Packages/macOS/CmuxFoundation/Sources/CmuxFoundation/ConfigPaths/CmuxGhosttyConfigSettingEditor.swift`
around lines 188 - 240, The write flow is duplicated between writeSetting and
writeTerminalFontFamily, both resolving the write URL, loading existing
contents, creating the target directory, and writing the updated text back.
Extract that shared sequence into a private helper in
CmuxGhosttyConfigSettingEditor that accepts a transform closure, then have
writeSetting and writeTerminalFontFamily pass updatedContents and
updatedTerminalFontFamilyContents respectively while keeping their existing
key-specific behavior in writeEditableSetting.
🤖 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.

Outside diff comments:
In
`@Packages/macOS/CmuxFoundation/Sources/CmuxFoundation/ConfigPaths/CmuxGhosttyConfigSettingEditor.swift`:
- Around line 188-240: The write flow is duplicated between writeSetting and
writeTerminalFontFamily, both resolving the write URL, loading existing
contents, creating the target directory, and writing the updated text back.
Extract that shared sequence into a private helper in
CmuxGhosttyConfigSettingEditor that accepts a transform closure, then have
writeSetting and writeTerminalFontFamily pass updatedContents and
updatedTerminalFontFamilyContents respectively while keeping their existing
key-specific behavior in writeEditableSetting.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 885e7540-2ec8-462c-9d58-1f230201bbdd

📥 Commits

Reviewing files that changed from the base of the PR and between ddbf005 and ef8dcac.

📒 Files selected for processing (5)
  • Packages/macOS/CmuxFoundation/Sources/CmuxFoundation/ConfigPaths/CmuxGhosttyConfigSettingEditor.swift
  • Packages/macOS/CmuxFoundation/Tests/CmuxFoundationTests/TerminalFontConfigEditorTests.swift
  • Packages/macOS/CmuxTerminalCore/Sources/CmuxTerminalCore/Config/GhosttyConfig.swift
  • Packages/macOS/CmuxTerminalCore/Tests/CmuxTerminalCoreTests/GhosttyConfigFontFamilyTests.swift
  • Sources/Settings/ConfigSource.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.

♻️ Duplicate comments (1)
Packages/macOS/CmuxSettingsUI/Sources/CmuxSettingsUI/Sections/AppearanceSection.swift (1)

197-250: 🗄️ Data Integrity & Integration | 🔵 Trivial | ⚡ Quick win

Save race is still reachable; also consolidate the four near-identical debounce helpers.

saveTerminalFontFamily/saveTerminalFontSize/saveSidebarFontSize/saveSurfaceTabBarFontSize cancel the previous wrapper Task and bump a generation counter, but the guard is only checked before the await hostActions.set… call. If a prior task has already passed its guard and is awaiting the real (I/O-bound) host write when a newer edit fires, cancelling it does not stop that in-flight write — it only suppresses the stale task's failure-flag update. Two overlapping setTerminalFontSize/setTerminalFontFamily calls can still complete out of order and let a stale value clobber the newer one on disk. This is the same class of issue flagged on a previous commit (marked "Addressed"), but the underlying pattern here does not actually serialize the writes.

Since all four methods share this exact structure, extracting one generic serialized/coalescing save helper (e.g. debouncedSave<T>(_ value: T, generation: inout Int, task: inout Task<Void, Never>?, failed: inout Bool, write: @escaping (T) async -> Bool)) would both remove the duplication and give you a single place to add real serialization (e.g. await the previous task before starting the write, or use an actor-owned queue) instead of four independent copies to fix.

Based on learnings, "for optimistic UI or CLI updates, keep one mutation path, record pending state with a request id or previous snapshot, reconcile from the authoritative result" — this generation-counter pattern only reconciles the UI flag, not the actual persisted value order.

♻️ Sketch of a consolidated helper
-    private func saveTerminalFontFamily(_ family: String) {
-        terminalFontFamilySaveGeneration += 1
-        let generation = terminalFontFamilySaveGeneration
-        terminalFontFamilySaveTask?.cancel()
-        terminalFontFamilySaveTask = Task {
-            await Task.yield()
-            guard !Task.isCancelled, generation == terminalFontFamilySaveGeneration else { return }
-            let saved = await hostActions.setTerminalFontFamily(family)
-            if !Task.isCancelled, generation == terminalFontFamilySaveGeneration { terminalFontFamilySaveFailed = !saved }
-        }
-    }
-
-    private func saveTerminalFontSize(_ points: Double) {
-        terminalFontSizeSaveGeneration += 1
-        let generation = terminalFontSizeSaveGeneration
-        terminalFontSizeSaveTask?.cancel()
-        terminalFontSizeSaveTask = Task {
-            await Task.yield()
-            guard !Task.isCancelled, generation == terminalFontSizeSaveGeneration else { return }
-            let saved = await hostActions.setTerminalFontSize(points)
-            if !Task.isCancelled, generation == terminalFontSizeSaveGeneration { terminalFontSizeSaveFailed = !saved }
-        }
-    }
+    private func save<Value>(
+        _ value: Value,
+        generation: inout Int,
+        task: inout Task<Void, Never>?,
+        failed: inout Bool,
+        write: `@escaping` (Value) async -> Bool
+    ) {
+        generation += 1
+        let expected = generation
+        let previous = task
+        task = Task {
+            await previous?.value // wait for the prior write to actually finish first
+            guard expected == generation else { return }
+            let saved = await write(value)
+            guard expected == generation else { return }
+            failed = !saved
+        }
+    }
🤖 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
`@Packages/macOS/CmuxSettingsUI/Sources/CmuxSettingsUI/Sections/AppearanceSection.swift`
around lines 197 - 250, The four save helpers in AppearanceSection are still
vulnerable to out-of-order persistence because cancellation and the generation
guard only protect the post-write failure flag, not the actual host write.
Update saveTerminalFontFamily, saveTerminalFontSize, saveSidebarFontSize, and
saveSurfaceTabBarFontSize so only one write path can run at a time and stale
tasks cannot complete after a newer request; use a shared serialized/coalescing
helper around hostActions.setTerminalFontFamily, setTerminalFontSize,
setSidebarFontSize, and setSurfaceTabBarFontSize. While fixing this, consolidate
the duplicated debounce logic into one generic helper so the serialization and
generation handling live in a single place.
🤖 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.

Duplicate comments:
In
`@Packages/macOS/CmuxSettingsUI/Sources/CmuxSettingsUI/Sections/AppearanceSection.swift`:
- Around line 197-250: The four save helpers in AppearanceSection are still
vulnerable to out-of-order persistence because cancellation and the generation
guard only protect the post-write failure flag, not the actual host write.
Update saveTerminalFontFamily, saveTerminalFontSize, saveSidebarFontSize, and
saveSurfaceTabBarFontSize so only one write path can run at a time and stale
tasks cannot complete after a newer request; use a shared serialized/coalescing
helper around hostActions.setTerminalFontFamily, setTerminalFontSize,
setSidebarFontSize, and setSurfaceTabBarFontSize. While fixing this, consolidate
the duplicated debounce logic into one generic helper so the serialization and
generation handling live in a single place.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: c3b2a1cf-0ccc-4675-b22e-cd9c48351a3e

📥 Commits

Reviewing files that changed from the base of the PR and between ef8dcac and dc962fc.

⛔ Files ignored due to path filters (1)
  • .github/swift-file-length-budget.tsv is excluded by !**/*.tsv
📒 Files selected for processing (5)
  • Packages/macOS/CmuxSettingsUI/Sources/CmuxSettingsUI/Environment/SettingsHostActions.swift
  • Packages/macOS/CmuxSettingsUI/Sources/CmuxSettingsUI/Navigation/SettingsSectionID.swift
  • Packages/macOS/CmuxSettingsUI/Sources/CmuxSettingsUI/Sections/AppearanceSection.swift
  • Packages/macOS/CmuxSettingsUI/Sources/CmuxSettingsUI/Sections/MonospacedFontFamilyCatalog.swift
  • Packages/macOS/CmuxSettingsUI/Sources/CmuxSettingsUI/Sections/SidebarSection.swift

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

This branch was successfully deployed

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

Labels

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add an Appearance settings tab (like Warp): terminal, sidebar, and workspace font/size in one place

3 participants