Skip to content

Open group config in the user's configured editor - #5250

Merged
austinywang merged 2 commits into
mainfrom
feat-group-config-preferred-editor
Jun 3, 2026
Merged

austinywang merged 2 commits into
mainfrom
feat-group-config-preferred-editor

Conversation

@austinywang

@austinywang austinywang commented Jun 2, 2026 •

Copy link
Copy Markdown
Contributor

Summary

The workspace-group Edit Group Configuration… action handed ~/.config/cmux/cmux.json to NSWorkspace.shared.open, which routes through Launch Services to the default .json handler (e.g. Antigravity) and ignores the user's preferredEditorCommand setting. The Open Config in External Editor Settings action had the same gap.

Both now route through the existing PreferredEditorSettings.open(_:) helper — already the single source of truth used by Open Cmux Settings File (openCmuxSettingsFileInEditor) and the Settings config window (ConfigSettingsView.openCurrentSourceInEditor) — so every cmux-config opener honors the configured editor and falls back to the OS default identically. This eliminates the "this opener forgot to honor the editor setting" class of bug.

openWorkspaceGroupsDocs() (web docs → browser) and revealCurrentSourceInFinder() (folder → Finder) are intentionally left on NSWorkspace.shared.open.

Tests

Adds PreferredEditorSettingsTests (Swift Testing) covering PreferredEditorSettings.resolvedCommand(defaults:) through its injected UserDefaults seam: returns the configured command (trimmed) when set, and nil when unset or blank (the OS-default fallback path). The actual open dispatch is Launch-Services/UI level and not unit-testable.

Localization

No user-facing strings changed — menu/Settings labels are untouched; the change only reroutes the open path and adds internal comments. No Localizable.xcstrings update needed.

🤖 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
Small behavior fix aligning two open actions with existing editor routing; no auth, data, or security surface changes.

Overview
Edit Group Configuration… and Open Config in External Editor no longer call NSWorkspace.shared.open on cmux.json. They now use PreferredEditorSettings.open, so preferredEditorCommand is honored instead of the system default .json app.

The sidebar opener gains a openCmuxConfigInEditor(home:open:) seam (production passes PreferredEditorSettings.open) so tests can assert the config URL is handed to the editor path. Docs and other non-config opens stay on NSWorkspace.

New PreferredEditorSettingsTests cover resolvedCommand (unset/blank → OS default, trimmed command when set). SidebarWorkspaceGroupConfigOpenerTests verify routing through the injected opener and empty-config materialization under a temp home.

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


Summary by cubic

Group and settings config files now open in the user's preferred editor instead of the OS .json default. This aligns all config openers and fixes the "opens in wrong app" bug.

  • Bug Fixes
    • Route “Edit Group Configuration…” and “Open Config in External Editor” through PreferredEditorSettings.open(_:) with OS-default fallback.
    • Add seam SidebarWorkspaceGroupConfigOpener.openCmuxConfigInEditor(home:open:) (defaults to PreferredEditorSettings.open) to ensure editor routing.
    • Keep docs (openWorkspaceGroupsDocs) and Finder reveal (revealCurrentSourceInFinder) on NSWorkspace.shared.open.
    • Tests: add PreferredEditorSettingsTests for resolvedCommand and SidebarWorkspaceGroupConfigOpenerTests to verify routing.

Written for commit 6e6f8c3. Summary will update on new commits.

Review in cubic

Summary by CodeRabbit

  • New Features

    • Configuration files now open using your preferred editor (honors configured editor) and will create an empty config file if none exists.
  • Tests

    • Added tests covering editor preference resolution, trimming/validation of configured commands, and routing the config file open operation through the selected editor.

@vercel

vercel Bot commented Jun 2, 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 2, 2026 11:37pm
cmux-staging Building Building Preview, Comment Jun 2, 2026 11:37pm

@coderabbitai

coderabbitai Bot commented Jun 2, 2026 •

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Config file opening refactored to route through PreferredEditorSettings.open() instead of OS defaults, honoring user-configured editor preferences. New helper function with injected open closure enables testing. Comprehensive tests validate resolver behavior for unset/empty/configured commands, whitespace trimming, and config file routing through the injected opener.

Changes

Preferred Editor Settings Integration

Layer / File(s) Summary
Config file opening via preferred editor
Sources/SidebarWorkspaceGroupConfigOpener.swift, Sources/HostSettingsActions.swift
openCmuxConfigInEditor() now delegates to a new openCmuxConfigInEditor(home:open:) helper that accepts an injected open closure, resolving and materializing ~/.config/cmux/cmux.json if missing and routing the open action via PreferredEditorSettings.open() rather than NSWorkspace.shared.open. HostSettingsActions.openConfigInExternalEditor() now uses PreferredEditorSettings.open(configFileURL).
Preferred editor resolver tests
cmuxTests/PreferredEditorSettingsTests.swift
Test suite validates PreferredEditorSettings.resolvedCommand() across unset, empty, whitespace-only, and configured command scenarios, plus whitespace trimming behavior. Uses isolated UserDefaults instances with cleared persistent domains.
Config opener integration test
cmuxTests/SidebarWorkspaceGroupConfigOpenerTests.swift
Regression test validates that openCmuxConfigInEditor(home:open:) routes the config file path through an injected opener closure, producing a cmux.json URL under home/.config/cmux and materializing an empty config file at that location.
Build project wiring for tests
cmux.xcodeproj/project.pbxproj
Xcode project updated to wire both new test files into the cmuxTests target via PBXBuildFile entries, PBXFileReference definitions, group membership, and PBXSourcesBuildPhase file lists.

🎯 2 (Simple) | ⏱️ ~10 minutes

🐰 A config file hops through editors true,
With preference settings all shiny and new,
Tests validate whitespace and empty commands clear,
The rabbit reviewed, no bugs to fear! ✨


Caution

Pre-merge checks failed

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

  • Ignore

❌ Failed checks (1 error, 1 warning)

Check name Status Explanation Resolution
Cmux Swift Logging ❌ Error File-scoped logger in HostSettingsActions.swift missing nonisolated keyword in MainActor-annotated file, violating swift-logging.md rules. Declare hostSettingsLogger as: nonisolated private let hostSettingsLogger = Logger(subsystem: "com.cmuxterm.app", category: "Settings")
Docstring Coverage ⚠️ Warning Docstring coverage is 16.67% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (16 passed)
Check name Status Explanation
Title check ✅ Passed The title directly summarizes the main change: routing config file opens through the user's preferred editor setting instead of the OS default.
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 Production code maintains proper actor isolation: HostSettingsActions is @MainActor, SidebarWorkspaceGroupConfigOpener is called from Views, matching project patterns.
Cmux Swift Blocking Runtime ✅ Passed No blocking primitives in production config-opener changes. PreferredEditorSettings.open defers process.waitUntilExit() to background thread only.
Cmux No Hacky Sleeps ✅ Passed PR contains only Swift code changes; the runtime-no-hacky-sleeps rule explicitly excludes Swift (covered by swift-blocking-runtime), and no problematic patterns exist in the changes.
Cmux Algorithmic Complexity ✅ Passed Production code changes involve only fixed-size O(1) operations on a single config file path; no loops, collection scans, batch rescans, or unbenchmarked algorithms over scalable data.
Cmux Swift Concurrency ✅ Passed PR introduces no new legacy async patterns; routes through PreferredEditorSettings.open, uses synchronous closure injection for tests, Task @MainActor is allowed AppKit boundary exception.
Cmux Swift @Concurrent ✅ Passed All modified Swift functions are synchronous or properly encapsulate async work; no @concurrent annotations added; no nonisolated async functions without proper isolation.
Cmux Swift File And Package Boundaries ✅ Passed Small refactors to focused files (44-line, 253-line): no oversizing, mixed responsibilities, or inappropriate app-target logic. Testable overload properly instrumented.
Cmux User-Facing Error Privacy ✅ Passed No user-facing errors, alerts, or sensitive information exposed in production code. Changes only route config opens through PreferredEditorSettings with silent fallback behavior.
Cmux Full Internationalization ✅ Passed PR introduces no new user-facing strings. Changes refactor config opening through PreferredEditorSettings.open (existing API). Tests only. No xcstrings modifications.
Cmux Swiftui State Layout ✅ Passed No SwiftUI state changes detected. PR modifies only non-SwiftUI file-handling code and adds unit tests; no @Published, @Observable, GeometryReader, or render-time state patterns introduced.
Cmux Architecture Rethink ✅ Passed PR consolidates config-file opening through single PreferredEditorSettings path (no timing, locks, observers, or duplicate state owners); clear ownership and invariant; proper test-only DI seam.
Cmux Swift Auxiliary Window Close Shortcuts ✅ Passed PR does not add or change standalone cmux windows. Changes only modify file-opening via PreferredEditorSettings.open() without creating NSWindow/NSPanel instances. Test fixtures are allowed.
Description check ✅ Passed The pull request description is comprehensive and covers all required template sections with relevant details.
✨ 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-group-config-preferred-editor

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 2, 2026 •

Copy link
Copy Markdown
Contributor

Greptile Summary

Routes the "Edit Group Configuration…" and "Open Config in External Editor" actions through PreferredEditorSettings.open(_:) instead of NSWorkspace.shared.open, so preferredEditorCommand is honored across all cmux config-file openers with a consistent OS-default fallback.

  • SidebarWorkspaceGroupConfigOpener gains a testable seam openCmuxConfigInEditor(home:open:); the public entry point passes the real home and PreferredEditorSettings.open. Line 35 now calls open(configURL) — the previously flagged P0 (closure never invoked) is fixed in this revision.
  • HostSettingsActions.openConfigInExternalEditor is a one-liner change from NSWorkspace.shared.open to PreferredEditorSettings.open.
  • New test suites (PreferredEditorSettingsTests, SidebarWorkspaceGroupConfigOpenerTests) verify resolver behavior and injection routing; both are wired into the Xcode test target.

Confidence Score: 5/5

Safe to merge — the change is narrowly scoped to routing two config-file open calls through an existing, well-tested helper, with no data model or auth impact.

Both affected callsites are straightforward one-liner substitutions that delegate to PreferredEditorSettings.open, which is already used by the other config openers. The previously flagged issue (injected closure never called in SidebarWorkspaceGroupConfigOpener) is correctly resolved in this revision: open(configURL) is called at line 35. New test suites cover the resolver logic and the injection routing end-to-end.

No files require special attention.

Important Files Changed

Filename Overview
Sources/SidebarWorkspaceGroupConfigOpener.swift Adds testable seam with injected open closure; public entry point correctly passes PreferredEditorSettings.open. Previously flagged P0 (hardcoded NSWorkspace.shared.open) is resolved — open(configURL) is called at line 35.
Sources/HostSettingsActions.swift Single-line fix: openConfigInExternalEditor now delegates to PreferredEditorSettings.open(configFileURL), consistent with the rest of the settings openers.
cmuxTests/PreferredEditorSettingsTests.swift New Swift Testing suite covering resolvedCommand for unset, blank/whitespace, and trimmed cases via injected UserDefaults. Clean isolation with per-test unique suite names.
cmuxTests/SidebarWorkspaceGroupConfigOpenerTests.swift Regression test that the injected open closure is called exactly once with the expected cmux.json URL under the injected temp home, and that the file is materialized on disk.
cmux.xcodeproj/project.pbxproj Adds PBXBuildFile, PBXFileReference, group membership, and Sources phase entries for both new test files; correct structure, no missing entries.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A["Edit Group Configuration… (menu)"] --> B["SidebarWorkspaceGroupConfigOpener\n.openCmuxConfigInEditor()"]
    C["Open Config in External Editor (Settings)"] --> D["HostSettingsActions\n.openConfigInExternalEditor()"]

    B --> E["openCmuxConfigInEditor(home:open:)\nMaterialize ~/.config/cmux/cmux.json if absent"]
    E --> F["open(configURL)\n↳ PreferredEditorSettings.open(_:)"]
    D --> F

    F --> G{preferredEditorCommand\nset & non-blank?}
    G -- Yes --> H["Launch configured editor\n(e.g. 'code', 'nvim')"]
    G -- No --> I["NSWorkspace.shared.open\n(OS default handler)"]

    style F fill:#d4edda,stroke:#28a745
    style G fill:#fff3cd,stroke:#ffc107
Loading

Reviews (3): Last reviewed commit: "Open cmux config files in the user's con..." | Re-trigger Greptile

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

No issues found across 4 files

Re-trigger cubic

austinywang and others added 2 commits June 2, 2026 16:15
The workspace-group "Edit Group Configuration…" opener calls
NSWorkspace.shared.open on ~/.config/cmux/cmux.json, which routes through
Launch Services to the default .json handler and ignores the user's
preferredEditorCommand setting.

Introduce a testable seam — openCmuxConfigInEditor(home:open:) — and a
regression test asserting the config file is routed through the injected
opener. The seam still calls NSWorkspace directly here, so the injected
opener is never invoked and the test fails: this commit is intentionally
red to prove the test catches the bug (fix follows).

Also add resolver coverage for PreferredEditorSettings.resolvedCommand.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Route both config-file openers through the editor-resolving path so they
honor preferredEditorCommand and fall back to the OS default — the same
semantics as "Open Cmux Settings File" and the Settings config window:

- SidebarWorkspaceGroupConfigOpener.openCmuxConfigInEditor (the
  workspace-group "Edit Group Configuration…" action) now hands the
  config file to the injected opener, which defaults to
  PreferredEditorSettings.open.
- HostSettingsActions.openConfigInExternalEditor likewise routes through
  PreferredEditorSettings.open instead of NSWorkspace.shared.open.

This turns the regression test added in the previous commit green and
eliminates the "this opener forgot to honor the editor setting" class of
bug. openWorkspaceGroupsDocs (web docs) stays on NSWorkspace, correctly
opening in a browser.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@austinywang
austinywang force-pushed the feat-group-config-preferred-editor branch from c8c57b5 to b1ce514 Compare June 2, 2026 23:16

@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 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 b1ce514. Configure here.

Comment thread Sources/SidebarWorkspaceGroupConfigOpener.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)
Sources/SidebarWorkspaceGroupConfigOpener.swift (1)

22-36: ⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

Injected open closure is never called — config still routes through NSWorkspace.

Line 35 calls NSWorkspace.shared.open(configURL) instead of the injected open closure. As written, the open parameter is dead, so:

  • The config file is still handed to Launch Services (OS default .json handler), defeating the entire purpose of this PR — preferredEditorCommand is not honored.
  • The regression test routesConfigFileThroughInjectedOpener will fail: opened stays empty, so #expect(opened.count == 1) cannot pass.
🐛 Proposed fix
-        NSWorkspace.shared.open(configURL)
+        open(configURL)
🤖 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/SidebarWorkspaceGroupConfigOpener.swift` around lines 22 - 36, The
injected opener closure passed to openCmuxConfigInEditor is never used — the
function calls NSWorkspace.shared.open(configURL) instead of invoking the
provided open closure; replace the direct call to NSWorkspace.shared.open with a
call to the injected open(configURL) so the preferredEditorCommand path is
exercised (ensure you still create the file and directories as before and pass
the same configURL variable to open). This change targets the
openCmuxConfigInEditor function and should make the
routesConfigFileThroughInjectedOpener test observe the injected opener being
invoked.
🤖 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 `@Sources/SidebarWorkspaceGroupConfigOpener.swift`:
- Around line 22-36: The injected opener closure passed to
openCmuxConfigInEditor is never used — the function calls
NSWorkspace.shared.open(configURL) instead of invoking the provided open
closure; replace the direct call to NSWorkspace.shared.open with a call to the
injected open(configURL) so the preferredEditorCommand path is exercised (ensure
you still create the file and directories as before and pass the same configURL
variable to open). This change targets the openCmuxConfigInEditor function and
should make the routesConfigFileThroughInjectedOpener test observe the injected
opener being invoked.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 5b49cf70-9f7f-42bd-ae56-e5365cc0edf2

📥 Commits

Reviewing files that changed from the base of the PR and between c8c57b5 and b1ce514.

📒 Files selected for processing (4)
  • Sources/SidebarWorkspaceGroupConfigOpener.swift
  • cmux.xcodeproj/project.pbxproj
  • cmuxTests/PreferredEditorSettingsTests.swift
  • cmuxTests/SidebarWorkspaceGroupConfigOpenerTests.swift

This branch was successfully deployed

1 active deployment
Preview – cmux — 6e6f8c3f Deployed Jun 2, 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