Skip to content

Fix copy-on-select Ghostty parity - #6200

Merged
austinywang merged 31 commits into
mainfrom
issue-6190-copy-on-select-parity
Jun 22, 2026
Merged

austinywang merged 31 commits into
mainfrom
issue-6190-copy-on-select-parity

Conversation

@austinywang

@austinywang austinywang commented Jun 16, 2026 •

Copy link
Copy Markdown
Contributor

Summary

  • stop emitting copy-on-select = false from cmux managed terminal config when the Copy on Selection toggle is off/default
  • preserve Ghostty/user copy-on-select = true so mouse selection updates the selection pasteboard used by Shift+Cmd+V
  • add regression coverage for Ghostty documented copy-on-select values and neighboring terminal settings so cmux managed config cannot clobber user Ghostty config
  • clarify Settings and schema text: this cmux toggle opts into system-clipboard copying, while Ghostty config/defaults continue to own selection-clipboard behavior when it is off

Trade-off

The existing Settings toggle stays a boolean for the current user-facing behavior: enabling it opts into Ghostty clipboard mode so selections also replace the system clipboard. Disabling/defaulting it now omits the managed Ghostty key instead of forcing false, so Ghostty's tri-state default and the user's own Ghostty config retain authority over selection-clipboard behavior.

Tests

  • git diff --check
  • git diff --cached --check
  • git diff --check origin/main...HEAD
  • ./scripts/lint-pbxproj-test-wiring.sh
  • python3 scripts/swift_file_length_budget.py --budget .github/swift-file-length-budget.tsv
  • /Users/austinwang/manaflow/cmuxterm-hq/skills/autoreview/scripts/cmux-policy-check --mode local
  • ./scripts/lint-ios-package-conventions.sh
  • arch -arm64 env PATH='/usr/bin:/bin:/usr/sbin:/sbin' xcodebuild -project cmux.xcodeproj -scheme cmux -configuration Debug -derivedDataPath /tmp/cmux-issue-6190-copy-on-select-parity-ui-tests-arm64 -destination 'platform=macOS,arch=arm64' build-for-testing -only-testing:cmuxUITests/SettingsTerminalBehaviorUITests
  • CMUX_SKIP_ZIG_BUILD=1 ./scripts/reload.sh --tag issue-6190-copy-on-select-parity

Blocked local check:

  • arch -arm64 xcrun swift test --package-path Packages/CmuxMobileShellUI --filter ComposerDictationTests currently fails before this branch's tests due the package declaring macOS 10.13 while dependencies require macOS 12/14.

Fixes #6190

Summary by CodeRabbit

  • Bug Fixes
    • Refined “Copy on Selection” behavior: when disabled, cmux no longer emits a Ghostty copy-on-select override, so Ghostty configuration controls clipboard behavior.
  • Documentation
    • Updated the “Copy on Selection” setting help/subtitle and schema descriptions across multiple languages to clearly explain true vs false behavior.
  • Tests
    • Added coverage for configuration layering to verify the effective copy-on-select value resolution.

@vercel

vercel Bot commented Jun 16, 2026 •

Copy link
Copy Markdown

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

Project Deployment Actions Updated (UTC)
cmux Canceled Canceled Jun 22, 2026 11:30am
cmux-staging Building Building Preview, Comment Jun 22, 2026 11:30am

@coderabbitai

coderabbitai Bot commented Jun 16, 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

ghosttyCopyOnSelectValue is changed to return nil instead of "false" when terminal.copyOnSelect is not explicitly true, so the managed Ghostty config omits the copy-on-select line entirely when disabled. GhosttyTerminalView passes emitsCopyOnSelectFalse: false at the call site. A regression test verifies that a user's copy-on-select = true value survives managed config layering. UI text and all locale message files are updated to describe the new true/false behavior.

Changes

copy-on-select managed config fix

Layer / File(s) Summary
ghosttyCopyOnSelectValue returns nil when disabled; call site passes emitsCopyOnSelectFalse: false
Sources/App/WorkspaceRuntimeSettings.swift, Sources/GhosttyTerminalView.swift
ghosttyCopyOnSelectValue now accepts emitsFalse parameter and returns "clipboard" only when the stored value is exactly true, and nil otherwise. ghosttyConfigContents gates the copy-on-select line on the non-nil return. TerminalManagedGhosttySettings gains emitsCopyOnSelectFalse parameter and forwards it. GhosttyTerminalView passes emitsCopyOnSelectFalse: false so the managed config never emits copy-on-select = false.
Regression test: user copy-on-select = true not downgraded
cmuxTests/TerminalCopyOnSelectManagedConfigLayeringTests.swift, cmux.xcodeproj/project.pbxproj
New @Suite sets copy-on-select to false in an isolated UserDefaults domain, then simulates layered config loading with a base "copy-on-select = true" string followed by the managed config. effectiveCopyOnSelectValue parses key = value lines and returns the last match; the test asserts the effective value is "true". The Xcode project is updated to compile the new file.
UI text and localization updates
Packages/CmuxSettingsUI/.../TerminalSection.swift, Resources/Localizable.xcstrings, web/data/cmux.schema.json, web/messages/en.json, web/messages/ja.json, web/messages/ar.json, web/messages/bs.json, web/messages/da.json, web/messages/de.json, web/messages/es.json, web/messages/fr.json, web/messages/it.json, web/messages/km.json, web/messages/ko.json, web/messages/no.json, web/messages/pl.json, web/messages/pt-BR.json, web/messages/ru.json, web/messages/th.json, web/messages/tr.json, web/messages/uk.json, web/messages/zh-CN.json, web/messages/zh-TW.json
The "Copy on Selection" setting subtitle, system-clipboard and selection-committed localized messages, and all locale copyOnSelect schema description strings are updated to describe: true copies selected terminal text to the system clipboard on commit; false causes cmux not to emit a Ghostty copy-on-select override, leaving behavior to Ghostty's own config and defaults.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~12 minutes

Possibly related PRs

  • manaflow-ai/cmux#4011: Introduced terminal.copyOnSelect → Ghostty copy-on-select config generation in WorkspaceRuntimeSettings.swift and GhosttyTerminalView, which is the code this PR directly modifies to fix the override regression.

Suggested reviewers

  • lawrencecchen

Poem

🐇 A bunny once wrote false into every config line,
Overriding the user's true — oh, what a crime!
Now nil is returned when the setting's turned away,
Ghostty keeps its defaults and ⇧⌘V saves the day.
No more stale selections, the clipboard's set free! ✨

🚥 Pre-merge checks | ✅ 20 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 12.50% 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
Title check ✅ Passed The title 'Fix copy-on-select Ghostty parity' directly and clearly summarizes the main objective: fixing a regression where cmux's managed copy-on-select configuration was not respecting Ghostty's tri-state behavior and was overriding user settings.
Linked Issues check ✅ Passed The PR successfully addresses all coding objectives from issue #6190: stops emitting copy-on-select = false from managed config when toggle is off (WorkspaceRuntimeSettings.swift), preserves user copy-on-select = true, adds regression test (TerminalCopyOnSelectManagedConfigLayeringTests.swift), and clarifies documentation/schema text across multiple files.
Out of Scope Changes check ✅ Passed All changes are directly scoped to the issue objectives: runtime settings logic modifications, documentation/string updates for clarification, schema descriptions, test additions, and Ghostty initialization updates. No unrelated code changes detected.
Cmux Swift Actor Isolation ✅ Passed All Swift production changes are parameter additions to pure utility enums with no stored state, called from synchronous initialization code. UI component already marked @MainActor. Test file compl...
Cmux Swift Blocking Runtime ✅ Passed PR introduces no blocking/timing-based synchronization in production Swift code. New test file uses deterministic test-only scaffolding (isolated UserDefaults, proper cleanup), which is explicitly...
Cmux Expensive Synchronous Load ✅ Passed The PR adds synchronous UserDefaults reads and string manipulation during GhosttyApp initialization, not expensive loaders like sysctl or directory scans prohibited by the rule.
Cmux Cache Substitution Correctness ✅ Passed The changes perform fresh reads from UserDefaults (the authoritative source) each time ghosttyConfigContents() is called. No cached values substitute authoritative reads. The code generates config...
Cmux No Hacky Sleeps ✅ Passed PR contains no TypeScript, JavaScript, shell, or build/runtime script changes—only Swift, JSON config, and Xcode project files. Check scope not applicable.
Cmux Algorithmic Complexity ✅ Passed All production code changes are O(1) operations (parameter passing, UserDefaults lookups, string formatting on fixed-size arrays). Test code is explicitly permitted by rules. No nested scans, unbou...
Cmux Swift Concurrency ✅ Passed No legacy async patterns violating concurrency modernization rules. WorkspaceRuntimeSettings and GhosttyTerminalView introduce only synchronous configuration code. TerminalSection's Task is properl...
Cmux Swift @Concurrent ✅ Passed No async/concurrent violations detected. All modified Swift functions are synchronous, pure utility functions with no async/await or @concurrent annotation changes.
Cmux Swift File And Package Boundaries ✅ Passed The PR is a focused bug fix with minimal changes: +7/-7 to WorkspaceRuntimeSettings (settings enums with single responsibility), +1 incidental Ghostty glue line to GhosttyTerminalView, +2/-2 UI tex...
Cmux Swift Logging ✅ Passed PR modifies only configuration parameter logic and localized strings; no print/debugPrint/dump/NSLog calls added to production code. Pre-existing debug-guarded NSLog at line 3817 is not changed.
Cmux User-Facing Error Privacy ✅ Passed All user-facing text in this PR complies with .github/review-bot-rules/user-facing-errors.md: no secrets, tokens, credentials, raw upstream messages, or internal implementation details exposed. Men...
Cmux Full Internationalization ✅ Passed All user-facing changes are fully internationalized: Swift strings use localized API with complete translations in 20 locales; web messages updated for all 20 locales in routing.ts; schema.json pro...
Cmux Swiftui State Layout ✅ Passed PR contains no SwiftUI state violations. Changes are utility functions, text updates, and initialization-time config loading. Existing @Observable patterns in TerminalSection remain unchanged.
Cmux Architecture Rethink ✅ Passed PR introduces a clean correctness fix with clear ownership and invariants: adds optional emitsFalse parameter to control whether disabled copy-on-select returns nil vs "false", preventing override...
Cmux Swift Auxiliary Window Close Shortcuts ✅ Passed PR adds/modifies only settings parameters, UI text, localization, schema docs, and regression tests with no window declarations or cmux.* identifier assignments.
Cmux Source Artifacts ✅ Passed All 30 changed files are legitimate source code, tests, configs, schemas, and localization catalogs with intentional product purpose—no source control artifacts detected.
Description check ✅ Passed PR description is comprehensive with clear summary, testing details, and tradeoffs, but lacks video demo and user verification sections from template.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch issue-6190-copy-on-select-parity

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.

@greptile-apps

greptile-apps Bot commented Jun 16, 2026 •

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR fixes copy-on-select parity with Ghostty by changing cmux's managed terminal config to omit copy-on-select = false when the toggle is off, rather than actively forcing it. Ghostty's own config and defaults now retain authority over selection-clipboard behavior when the cmux toggle is disabled.

  • Core logic change (WorkspaceRuntimeSettings.swift, GhosttyTerminalView.swift): adds an emitsFalse/emitsCopyOnSelectFalse parameter (defaulting to the old behavior for backward compatibility) and the production call site in loadCmuxManagedTerminalSettingsConfig now passes false, so a disabled toggle produces no Ghostty override at all.
  • Localization: all 18 non-English locales are updated in Localizable.xcstrings for both the subtitleOn and subtitleOff strings, and all 19 web/messages/ locale files are updated for schemaDescriptions.terminal.copyOnSelect, addressing the gap flagged in the previous review round.
  • Tests: a new TerminalCopyOnSelectManagedConfigLayeringTests suite verifies that documented Ghostty copy-on-select values (true, false, clipboard) are preserved when the cmux toggle is off, and that neighboring selection/clipboard settings are not clobbered.

Confidence Score: 5/5

Safe to merge — the change is narrowly scoped to omitting a Ghostty config key rather than forcing it, localizations are complete across all supported locales, and the new layering tests provide solid regression coverage.

The core production change is a single call-site flip from emitting copy-on-select = false to emitting nothing, with the new behavior validated by four dedicated layering tests. All 18 app locales and all 19 web message catalogs were updated consistently. No actor isolation, blocking, or logging issues were introduced. The CI workflow improvements are additive and CI-only.

No files require special attention.

Important Files Changed

Filename Overview
Sources/App/WorkspaceRuntimeSettings.swift Core fix: adds emitsFalse/emitsCopyOnSelectFalse parameters so a disabled toggle produces nil instead of "false", letting Ghostty config own selection-clipboard behavior. Logic is correct; default keeps old behavior for non-production callers.
Sources/GhosttyTerminalView.swift Single-line production call-site change: passes emitsCopyOnSelectFalse: false, activating the new omit-rather-than-emit behavior at the only place that loads managed terminal settings into Ghostty.
Resources/Localizable.xcstrings All 18 supported locales updated for both subtitleOff (new "cmux does not add…" text) and subtitleOn (added "also"), addressing the gap flagged in the prior review round.
cmuxTests/TerminalCopyOnSelectManagedConfigLayeringTests.swift New test suite covering disabled/unset/enabled toggle cases and neighboring-setting non-clobber regression. Tests are isolated with per-suite UserDefaults suites and correctly cleaned up via defer.
web/data/cmux.schema.json Schema description updated to document true/false behavior, including that false now omits the Ghostty override rather than forcing it.
.github/workflows/test-ios.yml CI-only improvement: refactors simctl calls into a helper, uses max() instead of sort+index, adds "iOS" name-string matching for broader runtime detection, and shares the created_name variable to avoid duplication.
Packages/macOS/CmuxSettingsUI/Sources/CmuxSettingsUI/Sections/TerminalSection.swift Subtitle default values updated to match the new localized strings; both on and off states correctly route through String(localized:defaultValue:).

Sequence Diagram

%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
    participant User as User (Settings)
    participant Defaults as UserDefaults
    participant TMGS as TerminalManagedGhosttySettings
    participant TCOS as TerminalCopyOnSelectSettings
    participant GhosttyApp as GhosttyApp
    participant Ghostty as Ghostty Config

    User->>Defaults: "set copyOnSelect = false (toggle off)"
    GhosttyApp->>TMGS: ghosttyConfigContents(emitsCopyOnSelectFalse: false)
    TMGS->>TCOS: ghosttyConfigContents(defaults, emitsFalse: false)
    TCOS->>TCOS: storedValue() → false
    TCOS-->>TMGS: nil (no override emitted)
    TMGS-->>GhosttyApp: nil → guard returns early
    Note over Ghostty: Ghostty config / defaults retain authority

    User->>Defaults: "set copyOnSelect = true (toggle on)"
    GhosttyApp->>TMGS: ghosttyConfigContents(emitsCopyOnSelectFalse: false)
    TMGS->>TCOS: ghosttyConfigContents(defaults, emitsFalse: false)
    TCOS->>TCOS: storedValue() → true
    TCOS-->>TMGS: "copy-on-select = clipboard"
    TMGS-->>GhosttyApp: "copy-on-select = clipboard"
    GhosttyApp->>Ghostty: loadInlineGhosttyConfig
    Note over Ghostty: System clipboard updated on every selection
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 User as User (Settings)
    participant Defaults as UserDefaults
    participant TMGS as TerminalManagedGhosttySettings
    participant TCOS as TerminalCopyOnSelectSettings
    participant GhosttyApp as GhosttyApp
    participant Ghostty as Ghostty Config

    User->>Defaults: "set copyOnSelect = false (toggle off)"
    GhosttyApp->>TMGS: ghosttyConfigContents(emitsCopyOnSelectFalse: false)
    TMGS->>TCOS: ghosttyConfigContents(defaults, emitsFalse: false)
    TCOS->>TCOS: storedValue() → false
    TCOS-->>TMGS: nil (no override emitted)
    TMGS-->>GhosttyApp: nil → guard returns early
    Note over Ghostty: Ghostty config / defaults retain authority

    User->>Defaults: "set copyOnSelect = true (toggle on)"
    GhosttyApp->>TMGS: ghosttyConfigContents(emitsCopyOnSelectFalse: false)
    TMGS->>TCOS: ghosttyConfigContents(defaults, emitsFalse: false)
    TCOS->>TCOS: storedValue() → true
    TCOS-->>TMGS: "copy-on-select = clipboard"
    TMGS-->>GhosttyApp: "copy-on-select = clipboard"
    GhosttyApp->>Ghostty: loadInlineGhosttyConfig
    Note over Ghostty: System clipboard updated on every selection
Loading

Reviews (26): Last reviewed commit: "Merge branch 'main' into issue-6190-copy..." | Re-trigger Greptile

Comment thread web/messages/en.json

@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 `@web/messages/en.json`:
- Around line 929-930: The copyOnSelect description in the English and Japanese
locale files has been expanded to include explanation of false-value behavior,
but the other 18 supported locale catalogs (zh-CN, zh-TW, ko, de, es, fr, it,
da, pl, ru, bs, ar, no, pt-BR, th, tr, km, uk) still contain the old shorter
description. Update the copyOnSelect terminal property description in all 18
remaining locale JSON files to match the new expanded version that explains what
happens when the value is false and how it relates to Ghostty copy-on-select
override behavior.
🪄 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: 6fd24acc-4ea8-4cde-97f5-a9a620be8327

📥 Commits

Reviewing files that changed from the base of the PR and between f9a6a75 and 210ed0c.

📒 Files selected for processing (9)
  • Packages/CmuxSettingsUI/Sources/CmuxSettingsUI/Sections/TerminalSection.swift
  • Resources/Localizable.xcstrings
  • Sources/App/WorkspaceRuntimeSettings.swift
  • cmux.xcodeproj/project.pbxproj
  • cmuxTests/AgentSessionAutoResumeSettingsTests.swift
  • cmuxTests/TerminalCopyOnSelectManagedConfigLayeringTests.swift
  • web/data/cmux.schema.json
  • web/messages/en.json
  • web/messages/ja.json
💤 Files with no reviewable changes (2)
  • Sources/App/WorkspaceRuntimeSettings.swift
  • cmuxTests/AgentSessionAutoResumeSettingsTests.swift

Comment thread web/messages/en.json

@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 `@Sources/App/WorkspaceRuntimeSettings.swift`:
- Around line 161-164: The `ghosttyCopyOnSelectValue` function lacks
documentation for the `emitsFalse` parameter, which controls critical behavior.
Add a docstring to the `ghosttyCopyOnSelectValue` function that documents what
the `emitsFalse` parameter does, specifically explaining that when set to
`false`, it omits the managed config line entirely, allowing user Ghostty config
and macOS defaults to take precedence. Include in the documentation that this
behavior fixes a regression where cmux would override user settings, so future
maintainers understand when and why to pass `false` versus `true`.
🪄 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: 6685a030-dbc1-4fd5-b934-5f1296709f78

📥 Commits

Reviewing files that changed from the base of the PR and between 210ed0c and 958d2fb.

📒 Files selected for processing (21)
  • Sources/App/WorkspaceRuntimeSettings.swift
  • Sources/GhosttyTerminalView.swift
  • cmuxTests/TerminalCopyOnSelectManagedConfigLayeringTests.swift
  • web/messages/ar.json
  • web/messages/bs.json
  • web/messages/da.json
  • web/messages/de.json
  • web/messages/es.json
  • web/messages/fr.json
  • web/messages/it.json
  • web/messages/km.json
  • web/messages/ko.json
  • web/messages/no.json
  • web/messages/pl.json
  • web/messages/pt-BR.json
  • web/messages/ru.json
  • web/messages/th.json
  • web/messages/tr.json
  • web/messages/uk.json
  • web/messages/zh-CN.json
  • web/messages/zh-TW.json

Comment thread Sources/App/WorkspaceRuntimeSettings.swift

This branch was successfully deployed

1 active deployment
Preview – cmux — c01f49e2 Deployed Jun 22, 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.

Regression: ⇧⌘V Paste Selection broken — cmux's managed copy-on-select overrides the user's copy-on-select = true (Ghostty parity)

1 participant