Skip to content

Add support for right Option modifier in key event handling - #2371

Closed
lejahmie wants to merge 9 commits into
manaflow-ai:mainfrom
lejahmie:bug-2369
Closed

lejahmie wants to merge 9 commits into
manaflow-ai:mainfrom
lejahmie:bug-2369

Conversation

@lejahmie

@lejahmie lejahmie commented Mar 30, 2026 •

Copy link
Copy Markdown

Summary

  • Fixed macOS side-specific Option handling in GhosttyNSView so macos-option-as-alt =
    right|left|true|false/unset behaves correctly.
  • Added a right-Option fallback path via flagsChanged keycode/state tracking for
    layouts/OS paths where raw right-Option modifier bits are unreliable.
  • Added regression tests in cmuxTests/CJKIMEInputTests.swift for left vs right Option
    behavior (Option+Delete and right-Option literal input).

Why:

Fix: #2369

  • cmux was reading Ghostty config but could miss right-side Alt semantics, causing ⌥R+d
    to produce layout text (∂) instead of Meta (^[d) under macos-option-as-alt = right.

Testing

  • Build/dev validation: ./scripts/setup.sh && ./scripts/reload.sh --tag
    fix-right-option-alt
  • Manual verification (macOS
    26.2, ISO International keyboard):
  • macos-option-as-alt = right works (⌥R+d => ^[d in od -An -t x1)
  • left, true, and unset/default behaviors validated
  • Added unit regressions:
  • testRightOptionDeleteSetsAltRightModifier
  • testRightOptionLiteralCharacterRetainsAltRightInConsumedMods

Review Trigger (Copy/Paste as PR comment)

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

Checklist

  • I tested the change locally
  • I added or updated tests for behavior changes
  • I updated docs/changelog if needed
  • I requested bot reviews after my latest commit (copy/paste block above or
    equivalent)
  • All code review bot comments are resolved
  • All human review comments are resolved

Summary by cubic

Fixes bug 2369 by reliably handling the right Option key on macOS so macos-option-as-alt=right works and Meta combos behave (e.g., ⌥R+d → ^[d). Adds side-aware detection, a robust fallback, unified key dispatch, and debug hooks to simulate right‑Option release while left is held.

  • Bug Fixes

    • Detect right Option via NX_DEVICERALTKEYMASK; fallback infers using flagsChanged and kVK_RightOption with a one‑shot toggle.
    • Set ALT_RIGHT in raw mods; include it in consumed_mods only for literal input. Non‑printing keys (e.g., Option+Delete) consume none. Left Option never sets ALT_RIGHT.
    • Clear right‑Option state on Option up, focus loss, and when a right‑Option “release” occurs while left Option remains down.
  • Refactors

    • Centralize modifier logic in translatedModifierFlags(...), consumedModsFromFlags(...), and isRightOptionActive(...); propagate AltRight into AppKit flags.
    • Route key sends through sendGhosttyKey.
    • Add debug APIs on GhosttyNSView/GhosttySurfaceScrollView to set right‑Option state and simulate flagsChanged.

Written for commit dd5ae09. Summary will update on new commits.

Summary by CodeRabbit

  • Bug Fixes

    • Right-Option detection is more reliable and distinguished from Left Option; Option combos (including Option+Delete), literal Right-Option input, and focus-loss/focus-gain clearing behave consistently.
  • Refactor

    • Unified modifier-translation and key-dispatch paths for consistent handling of modifier state.
  • Tests

    • Added and refactored tests covering Right-Option detection, Option+Delete, literal Right-Option input, and release scenarios.
  • Debug

    • Added debug-only UI hooks to simulate Right-Option state and flag-change events for UI testing.

@vercel

vercel Bot commented Mar 30, 2026

Copy link
Copy Markdown

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

A member of the Team first needs to authorize it.

@lejahmie

Copy link
Copy Markdown
Author

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

@cubic-dev-ai

cubic-dev-ai Bot commented Mar 30, 2026

Copy link
Copy Markdown

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

@lejahmie I have started the AI code review. It will take a few minutes to complete.

@coderabbitai

coderabbitai Bot commented Mar 30, 2026

Copy link
Copy Markdown

@lejahmie Sure, I'll review the changes in this PR right away!


🧠 Learnings used
Learnt from: BillionClaw
Repo: manaflow-ai/cmux PR: 1755
File: Sources/GhosttyTerminalView.swift:4889-4903
Timestamp: 2026-03-18T16:50:14.396Z
Learning: Repo: manaflow-ai/cmux — In Sources/GhosttyTerminalView.swift, GhosttyNSView.performKeyEquivalent(with:) must only route Command key equivalents to NSApp.mainMenu when shouldRouteCommandEquivalentDirectlyToMainMenu(event) returns true; otherwise, handle the key directly via keyDown to forward unbound Cmd+Shift combinations to the terminal and preserve the Cmd+` exclusion.

Learnt from: alumican
Repo: manaflow-ai/cmux PR: 2051
File: Sources/AppDelegate.swift:9270-9276
Timestamp: 2026-03-24T13:50:07.535Z
Learning: Repo: manaflow-ai/cmux — KeyboardShortcutSettings.Action.toggleTextBoxInput defaults to Cmd+Option+B. Rationale: avoid conflict with the existing hard-coded Cmd+Option+T binding that triggers closeOtherTabsInFocusedPaneWithConfirmation() in AppDelegate.handleCustomShortcut(event:).

Learnt from: johnhanks1
Repo: manaflow-ai/cmux PR: 1556
File: Sources/AppDelegate.swift:7878-7888
Timestamp: 2026-03-17T01:11:15.590Z
Learning: Repo: manaflow-ai/cmux — Sources/AppDelegate.swift — In AppDelegate.handleCustomShortcut(event:), when detecting an external keyboard input-source change (via KeyboardLayout.id), the skip branch must not update lastKeyboardInputSourceId; it should only log and return false. Update lastKeyboardInputSourceId only after passing the guard into normal shortcut handling. Rationale: prevents same-event re-entry (e.g., via handleBrowserSurfaceKeyEquivalent from NSWindow.performKeyEquivalent) from bypassing the skip and inadvertently processing app shortcuts.

Learnt from: jt-hsiao
Repo: manaflow-ai/cmux PR: 1423
File: Sources/AppDelegate.swift:11220-11226
Timestamp: 2026-03-14T07:06:01.466Z
Learning: Repo: manaflow-ai/cmux — In Sources/Panels/CmuxWebView.swift, performKeyEquivalent(with:) handles Command-key routing end-to-end: (1) if allowed, route to NSApp.mainMenu.performKeyEquivalent; (2) fall back to AppDelegate.shared?.handleBrowserSurfaceKeyEquivalent(event) for non–menu-backed app shortcuts; (3) fall back to super.performKeyEquivalent. For non-Command keys it calls super directly. Therefore, in NSWindow.cmux_performKeyEquivalent (Sources/AppDelegate.swift), it is correct to call firstResponderWebView.performKeyEquivalent and return its Bool unconditionally to avoid re-entering SwiftUI’s performKeyEquivalent path that can swallow keys after WKWebView focus.

Learnt from: jt-hsiao
Repo: manaflow-ai/cmux PR: 1423
File: Sources/AppDelegate.swift:11220-11226
Timestamp: 2026-03-14T07:06:01.466Z
Learning: Repo: manaflow-ai/cmux — Command-backtick (Cmd+`, keyCode 50) is intentionally excluded from direct menu routing by shouldRouteCommandEquivalentDirectlyToMainMenu; tests assert this behavior. Do not add a window/main-menu bypass for Cmd+`.

Learnt from: lawrencecchen
Repo: manaflow-ai/cmux PR: 318
File: Sources/Panels/BrowserPanel.swift:0-0
Timestamp: 2026-03-17T05:34:44.905Z
Learning: In manaflow-ai/cmux (PR `#318`), the browser import wizard in Sources/Panels/BrowserPanel.swift now exposes an Additional data checkbox and passes its state into BrowserImportScope.fromSelection so users can select .everything (including when only additional-data is chosen). Unit and UI tests cover the additional‑data‑only mapping.

Learnt from: qkrwpdlr
Repo: manaflow-ai/cmux PR: 0
File: :0-0
Timestamp: 2026-03-23T07:12:42.553Z
Learning: Repo: manaflow-ai/cmux — In Sources/TerminalController.swift and Sources/Panels/BrowserPanel.swift, BiDi override (U+202A–202E, U+2066–2069) and zero-width char (U+200B–200F, U+FEFF) filtering is implemented via a shared `dangerousScalars: Set<UInt32>` in each class. v2SanitizeWebText() truncates to 200 chars; v2SanitizeXPath() caps at 2000 chars for selector fidelity. Both use a shared v2SanitizeScalar() predicate. BrowserPickerMessageHandler.sanitize() uses the same dangerousScalars pattern with a 200-char cap.

Learnt from: outoftime
Repo: manaflow-ai/cmux PR: 1528
File: Sources/GhosttyTerminalView.swift:3220-3228
Timestamp: 2026-03-17T18:25:33.286Z
Learning: Repo: manaflow-ai/cmux — In Sources/GhosttyTerminalView.swift within TerminalSurface.createSurface(for:), when constructing XDG_DATA_DIRS for Fish vendor_conf.d auto-sourcing, treat empty or whitespace-only values from initialEnvironmentOverrides, env, getenv, and ProcessInfo as unset before prefixing the integrationDir. This avoids producing a trailing colon. Keep XDG_DATA_DIRS in protectedStartupEnvironmentKeys so initialEnvironmentOverrides cannot overwrite the prefixed value.
✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai

coderabbitai Bot commented Mar 30, 2026 •

Copy link
Copy Markdown
📝 Walkthrough

Walkthrough

Adds explicit right-Option state tracking and UI-test hooks in the view layer, centralizes modifier translation/consumption (including side-specific Option detection), consolidates key dispatch via sendGhosttyKey, and updates tests to validate left/right Option behaviors and fallback flows.

Changes

Cohort / File(s) Summary
Core view & modifier logic
Sources/GhosttyTerminalView.swift
Add explicit Right Option state (rightOptionModifierDown, rightOptionFallbackToggleArmed), reset/arm on focus changes, replace inline AppKit flag sync with translatedModifierFlags(from:fallback:), extend consumedModsFromFlags(_:, sourceEvent:), add rightOptionModifierFlag mapping, infer Right Option transitions in flagsChanged(with:), standardize key dispatch to sendGhosttyKey, and add DEBUG-only UI-test hooks including debugSimulateFlagsChangedForUITest.
Scroll view wrapper
Sources/.../GhosttySurfaceScrollView.swift
Forward DEBUG-only Right Option UI-test hooks from GhosttyNSView to higher-level callers.
Tests — Option/Delete and Right-Option
cmuxTests/CJKIMEInputTests.swift
Import Carbon; add captureOptionDeletePressEvent helper; refactor Option+Delete tests; add tests for Right-Option raw/consumed mod assertions, literal-character fallback behavior, and simulated Right-Option release using the new debug hooks.

Sequence Diagram(s)

sequenceDiagram
    participant User as User
    participant View as GhosttyNSView
    participant Translator as ModifierTranslator
    participant Sender as GhosttyInputSender
    participant Core as TerminalCore

    rect rgba(200,200,255,0.5)
    User->>View: press/release key (incl. left/right Option)
    end

    View->>View: flagsChanged(with: event) -> update rightOptionModifierDown / fallback toggle
    View->>Translator: translatedModifierFlags(from: flags, fallback:)
    Translator->>Translator: consumed = consumedModsFromFlags(flags, sourceEvent: event)
    View->>Sender: sendGhosttyKey(keyEvent, translatedMods, consumed)
    Sender->>Core: deliver ghostty_input_key_s to terminal core
    Core-->>User: terminal receives input / characters
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Poem

🐰 I twitch a whisker, track the right,
Left types letters, right takes flight.
Flags flip, tests hop through the night,
Keys find home — a bunny's delight.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 3.85% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title 'Add support for right Option modifier in key event handling' accurately describes the main change—enabling right Option key distinction and handling in the GhosttyNSView key event system.
Description check ✅ Passed The description includes all key template sections: a Summary explaining what changed and why, detailed Testing with build/manual verification steps, and a complete Checklist with most items checked, satisfying core template requirements.
Linked Issues check ✅ Passed The PR directly addresses issue #2369 by implementing side-specific Option handling via NX_DEVICERALTKEYMASK detection, a fallback via flagsChanged tracking, and right-Option literal/consumed-mods logic, enabling macos-option-as-alt = right to work correctly.
Out of Scope Changes check ✅ Passed All changes are scoped to right Option modifier support: GhosttyNSView tracking/state management, modifier translation refactoring, debug UI-test hooks, and regression tests—with no unrelated modifications detected.

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

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

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

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

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

No issues found across 2 files

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. More of your lovely PRs please.

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@greptile-apps

greptile-apps Bot commented Mar 30, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR fixes right-Option (\u29c5R) modifier handling in GhosttyNSView so that macos-option-as-alt = right behaves correctly. It introduces GHOSTTY_MODS_ALT_RIGHT tracking via NX_DEVICERALTKEYMASK and a toggle-based fallback for keyboards/OS combinations that don't report that bit, extracts the duplicated mod-translation loop into translatedModifierFlags(), and adds two XCTest regression cases.

  • rightOptionModifierFlag static constant wraps NX_DEVICERALTKEYMASK for right-Option bit detection
  • rightOptionModifierDown tracks fallback state via flagsChanged, reset on resignFirstResponder
  • modsFromEvent and consumedModsFromFlags now include GHOSTTY_MODS_ALT_RIGHT when isRightOptionActive returns true
  • translatedModifierFlags() replaces two identical 30-line mod-mapping loops
  • testRightOptionLiteralCharacterRetainsAltRightInConsumedMods passes rightOptionModifierFlag in both the event flags and via debugSetRightOptionModifierDownForUITest(true), so the rightOptionModifierDown fallback path is never actually exercised
  • testRightOptionDeleteSetsAltRightModifier is missing consumed_mods and text == nil assertions present in the sibling test
  • The toggle() heuristic is correct under normal single-call-per-edge delivery but could desync under a buggy HID driver
  • CLAUDE.md specifies a two-commit structure for regression tests (failing test first, fix second); this PR bundles both together

Confidence Score: 5/5

Safe to merge — production logic is correct; all remaining findings are P2 style and test-coverage improvements

The core right-Option detection and modifier-propagation logic is sound: NX_DEVICERALTKEYMASK is the established macOS API for this purpose, the toggle heuristic correctly handles press/release edges under normal event delivery, and translatedModifierFlags() removes duplication without altering behaviour. No P0/P1 defects were found. The three P2 issues are non-blocking.

cmuxTests/CJKIMEInputTests.swift — the literal-character test does not cover the rightOptionModifierDown fallback path it implies, and the delete test is missing consumed_mods and text assertions

Important Files Changed

Filename Overview
Sources/GhosttyTerminalView.swift Adds right-Option detection via NX_DEVICERALTKEYMASK and toggle-based fallback; refactors duplicated mod-translation loops into translatedModifierFlags(); logic is sound
cmuxTests/CJKIMEInputTests.swift Extracts captureOptionDeletePressEvent helper and adds two right-Option regression tests; literal-character test never exercises the rightOptionModifierDown fallback path, delete test missing consumed_mods assertions

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    FC[flagsChanged event] --> KC{keyCode?}
    KC -->|kVK_RightOption| RO_BIT{hasRightOptionBit?}
    KC -->|kVK_Option| LO_BIT{hasRightOptionBit?}
    KC -->|other| OD3{optionDown?}
    RO_BIT -->|yes| SET_TRUE[rightOptionModifierDown = true]
    RO_BIT -->|no| OD1{optionDown?}
    OD1 -->|no| SET_FALSE1[rightOptionModifierDown = false]
    OD1 -->|yes| TOGGLE[rightOptionModifierDown.toggle]
    LO_BIT -->|yes| SET_TRUE2[rightOptionModifierDown = true]
    LO_BIT -->|no| OD2{optionDown?}
    OD2 -->|no| SET_FALSE2[rightOptionModifierDown = false]
    OD2 -->|yes| NOOP[no change]
    OD3 -->|no| SET_FALSE3[rightOptionModifierDown = false]
    OD3 -->|yes| NOOP2[no change]
    SET_TRUE --> MODS
    SET_FALSE1 --> MODS
    TOGGLE --> MODS
    SET_TRUE2 --> MODS
    SET_FALSE2 --> MODS
    NOOP --> MODS
    SET_FALSE3 --> MODS
    NOOP2 --> MODS
    MODS[modsFromEvent / isRightOptionActive] --> IRA{detection path?}
    IRA -->|NX_DEVICERALTKEYMASK bit set| ALT_RIGHT[GHOSTTY_MODS_ALT_RIGHT]
    IRA -->|keyCode == kVK_RightOption| ALT_RIGHT
    IRA -->|keyCode == kVK_Option| ALT_ONLY[GHOSTTY_MODS_ALT only]
    IRA -->|fallback: rightOptionModifierDown| ALT_RIGHT
    IRA -->|else| ALT_ONLY
Loading

Reviews (1): Last reviewed commit: "Add support for right Option modifier in..." | Re-trigger Greptile

Comment thread cmuxTests/CJKIMEInputTests.swift
Comment thread cmuxTests/CJKIMEInputTests.swift
Comment thread Sources/GhosttyTerminalView.swift

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 2

Caution

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

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

6171-6200: ⚠️ Potential issue | 🟡 Minor

Record the fallback right-Option state before checking surface.

Line 6172 exits before rightOptionModifierDown is updated. Since ensureSurfaceReadyForInput() can create or reattach the surface on the following keyDown, the first right-Option chord after startup or reparent can still lose its side information on layouts that only report generic .option.

Suggested fix
 override func flagsChanged(with event: NSEvent) {
-    guard let surface = surface else {
-        super.flagsChanged(with: event)
-        return
-    }
-
     let optionDown = event.modifierFlags.contains(.option)
     let hasRightOptionBit = event.modifierFlags.contains(Self.rightOptionModifierFlag)
     switch Int(event.keyCode) {
     case Int(kVK_RightOption):
         if hasRightOptionBit {
@@
         if !optionDown {
             rightOptionModifierDown = false
         }
     }
+
+    guard let surface = surface else {
+        super.flagsChanged(with: event)
+        return
+    }
 
     var keyEvent = ghostty_input_key_s()
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Sources/GhosttyTerminalView.swift` around lines 6171 - 6200, Compute and
store the inferred right-Option state before early-returning when surface is
nil: in flagsChanged(with:) evaluate optionDown and hasRightOptionBit and derive
the fallback right-side value (the same inference logic you use later for
Int(kVK_RightOption)/Int(kVK_Option)/default) into a local variable and assign
it to rightOptionModifierDown before the guard that checks surface so the first
right-Option chord after startup/reparent retains side information; keep the
existing per-key handling after the guard (and still call
super.flagsChanged(with:) when returning), and ensure this change works with
ensureSurfaceReadyForInput() behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@cmuxTests/CJKIMEInputTests.swift`:
- Around line 1293-1330: The test sets a sticky override via
hostedView.surfaceView.debugSetRightOptionModifierDownForUITest(true) but resets
it later in the function, which can be skipped on early returns and leak state;
wrap the reset in a defer immediately after enabling the override (i.e., call
defer { hostedView.surfaceView.debugSetRightOptionModifierDownForUITest(false)
}) so the override is always cleared even on failures, and keep the existing
debugGhosttySurfaceKeyEventObserver and capturedPress logic unchanged.

In `@Sources/GhosttyTerminalView.swift`:
- Around line 6235-6292: consumedModsFromFlags currently only sets
GHOSTTY_MODS_ALT when flags contains .option, which can drop
GHOSTTY_MODS_ALT_RIGHT; change it to always compute rightOptionActive (using
isRightOptionActive) and set GHOSTTY_MODS_ALT_RIGHT when rightOptionActive is
true, but only set GHOSTTY_MODS_ALT when flags.contains(.option) (keep existing
behavior for ALT bit); also update isRightOptionActive(event:flags:) to treat a
right-option keyUp correctly by checking event.type (return false for keyUp when
flags no longer contain .option and Int(event.keyCode) == kVK_RightOption) so a
right-option release doesn't leave rightOptionModifierDown implied.

---

Outside diff comments:
In `@Sources/GhosttyTerminalView.swift`:
- Around line 6171-6200: Compute and store the inferred right-Option state
before early-returning when surface is nil: in flagsChanged(with:) evaluate
optionDown and hasRightOptionBit and derive the fallback right-side value (the
same inference logic you use later for
Int(kVK_RightOption)/Int(kVK_Option)/default) into a local variable and assign
it to rightOptionModifierDown before the guard that checks surface so the first
right-Option chord after startup/reparent retains side information; keep the
existing per-key handling after the guard (and still call
super.flagsChanged(with:) when returning), and ensure this change works with
ensureSurfaceReadyForInput() 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: defaults

Review profile: CHILL

Plan: Pro

Run ID: 5b16e956-1729-4f20-9340-87d6d0ddb139

📥 Commits

Reviewing files that changed from the base of the PR and between 9d9559f and 205e873.

📒 Files selected for processing (2)
  • Sources/GhosttyTerminalView.swift
  • cmuxTests/CJKIMEInputTests.swift

Comment thread cmuxTests/CJKIMEInputTests.swift Outdated
Comment thread Sources/GhosttyTerminalView.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)
Sources/GhosttyTerminalView.swift (1)

6235-6294: ⚠️ Potential issue | 🟠 Major

ALT_RIGHT still falls out of the translated path.

Line 6290 can preserve rightOptionModifierFlag even when .option is cleared, but Line 6264 returns before that bit is checked. That means consumedModsFromFlags(..., sourceEvent:) can still emit NONE for right-Option literal input, and Line 6266 still makes a kVK_RightOption keyUp look right-active if left Option remains held.

💡 Proposed fix
 private func isRightOptionActive(event: NSEvent, flags: NSEvent.ModifierFlags) -> Bool {
-    if event.type == .keyUp,
-       Int(event.keyCode) == Int(kVK_RightOption),
-       !flags.contains(.option) {
+    if event.type == .keyUp,
+       Int(event.keyCode) == Int(kVK_RightOption) {
         return false
     }
-    guard flags.contains(.option) else { return false }
     if flags.contains(Self.rightOptionModifierFlag) { return true }
+    guard flags.contains(.option) else { return false }
     if Int(event.keyCode) == Int(kVK_RightOption) { return true }
     if Int(event.keyCode) == Int(kVK_Option) { return false }
     return rightOptionModifierDown
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Sources/GhosttyTerminalView.swift` around lines 6235 - 6294,
consumedModsFromFlags and isRightOptionActive currently let the absence of
.option short-circuit detection of a physically active right-Option, causing
GHOSTTY_MODS_ALT_RIGHT to be lost; update isRightOptionActive (used by
consumedModsFromFlags) to remove the early guard "guard flags.contains(.option)
else { return false }" and instead only keep the existing keyUp special-case
(the if checking event.type == .keyUp && keyCode == kVK_RightOption &&
!flags.contains(.option) return false), then evaluate
flags.contains(Self.rightOptionModifierFlag), Int(event.keyCode) ==
kVK_RightOption, Int(event.keyCode) == kVK_Option (return false), and finally
fallback to rightOptionModifierDown so that GHOSTTY_MODS_ALT_RIGHT can be
emitted even when .option is not present in flags.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@Sources/GhosttyTerminalView.swift`:
- Around line 6235-6294: consumedModsFromFlags and isRightOptionActive currently
let the absence of .option short-circuit detection of a physically active
right-Option, causing GHOSTTY_MODS_ALT_RIGHT to be lost; update
isRightOptionActive (used by consumedModsFromFlags) to remove the early guard
"guard flags.contains(.option) else { return false }" and instead only keep the
existing keyUp special-case (the if checking event.type == .keyUp && keyCode ==
kVK_RightOption && !flags.contains(.option) return false), then evaluate
flags.contains(Self.rightOptionModifierFlag), Int(event.keyCode) ==
kVK_RightOption, Int(event.keyCode) == kVK_Option (return false), and finally
fallback to rightOptionModifierDown so that GHOSTTY_MODS_ALT_RIGHT can be
emitted even when .option is not present in flags.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 1df96d5e-f844-4abb-9a6c-09211d0a64e8

📥 Commits

Reviewing files that changed from the base of the PR and between 205e873 and b235063.

📒 Files selected for processing (2)
  • Sources/GhosttyTerminalView.swift
  • cmuxTests/CJKIMEInputTests.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.

🧹 Nitpick comments (1)
cmuxTests/CJKIMEInputTests.swift (1)

1298-1300: Consider renaming rightOptionOnly for clarity.

The variable is assigned [.option] without the device-specific right flag. The "right-Option" semantics come from debugSetRightOptionModifierDownForUITest(true) which sets the internal fallback state. This mirrors the real-world scenario where macOS doesn't reliably report right-Option in modifier flags for some layouts/OS paths.

Renaming to optionOnly or adding a brief comment would reduce confusion:

-let rightOptionOnly = NSEvent.ModifierFlags([.option])
+// .option only; right-Option semantics injected via debugSetRightOptionModifierDownForUITest
+let optionOnly = NSEvent.ModifierFlags([.option])
 hostedView.debugSetRightOptionModifierDownForUITest(true)
 defer { hostedView.debugSetRightOptionModifierDownForUITest(false) }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@cmuxTests/CJKIMEInputTests.swift` around lines 1298 - 1300, The variable name
rightOptionOnly is misleading because it is constructed as
NSEvent.ModifierFlags([.option]) (not the device-specific right flag) while the
"right option" behavior is produced by calling
hostedView.debugSetRightOptionModifierDownForUITest(true); rename
rightOptionOnly to optionOnly (or similar) and/or add a one-line comment near
the declaration explaining that the flags use .option and that
debugSetRightOptionModifierDownForUITest(true) provides the right-Option
semantics; update any references to rightOptionOnly accordingly and keep the
defer call to debugSetRightOptionModifierDownForUITest(false) unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@cmuxTests/CJKIMEInputTests.swift`:
- Around line 1298-1300: The variable name rightOptionOnly is misleading because
it is constructed as NSEvent.ModifierFlags([.option]) (not the device-specific
right flag) while the "right option" behavior is produced by calling
hostedView.debugSetRightOptionModifierDownForUITest(true); rename
rightOptionOnly to optionOnly (or similar) and/or add a one-line comment near
the declaration explaining that the flags use .option and that
debugSetRightOptionModifierDownForUITest(true) provides the right-Option
semantics; update any references to rightOptionOnly accordingly and keep the
defer call to debugSetRightOptionModifierDownForUITest(false) unchanged.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 73677fd4-ae80-467d-8b89-47a611a7bd3f

📥 Commits

Reviewing files that changed from the base of the PR and between b235063 and 0ea10f0.

📒 Files selected for processing (2)
  • Sources/GhosttyTerminalView.swift
  • cmuxTests/CJKIMEInputTests.swift
🚧 Files skipped from review as they are similar to previous changes (1)
  • Sources/GhosttyTerminalView.swift

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

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

Inline comments:
In `@cmuxTests/CJKIMEInputTests.swift`:
- Around line 1325-1335: The test currently asserts only capturedPress.mods;
also assert that capturedPress.consumed_mods contains the GHOSTTY_MODS_ALT_RIGHT
bit so printable right-Option input preserves the AltRight consumption. Locate
the assertions around capturedPress.mods in CJKIMEInputTests (the test method
using capturedPress) and add an XCTAssertEqual or XCTAssertTrue checking
(capturedPress.consumed_mods.rawValue & GHOSTTY_MODS_ALT_RIGHT.rawValue) ==
GHOSTTY_MODS_ALT_RIGHT.rawValue to ensure consumed_mods carries the AltRight
bit.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 5b1c855d-4d24-45f1-a07e-35ea33baaa88

📥 Commits

Reviewing files that changed from the base of the PR and between 1856771 and 159bd95.

📒 Files selected for processing (1)
  • cmuxTests/CJKIMEInputTests.swift

Comment thread cmuxTests/CJKIMEInputTests.swift
@lejahmie

Copy link
Copy Markdown
Author

All automated PR review feedback adressed. Manual tests done, unit tests pass.

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

1 issue found across 2 files (changes from recent commits).

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="Sources/GhosttyTerminalView.swift">

<violation number="1" location="Sources/GhosttyTerminalView.swift:6191">
P2: Right‑Option fallback can get stuck true when right Option is released while left Option remains held, because the one‑shot toggle is never re‑armed or cleared on that release. This can misclassify later left‑Option shortcuts as right‑Option.</violation>
</file>

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

Comment thread Sources/GhosttyTerminalView.swift
@lejahmie

Copy link
Copy Markdown
Author

@coderabbitai resume

@coderabbitai

coderabbitai Bot commented Mar 31, 2026

Copy link
Copy Markdown
✅ Actions performed

Reviews resumed.

@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)
Sources/GhosttyTerminalView.swift (1)

6178-6211: ⚠️ Potential issue | 🟠 Major

Right-Option fallback still desynchronizes side state.

Line 6191 only handles the ambiguous kVK_RightOption transition once, so releasing right Option while left Option stays down never clears rightOptionModifierDown. Line 6280 then returns before checking rightOptionModifierFlag, so a translated right-only flag set can still lose ALT_RIGHT. That leaves mods/consumed_mods wrong in exactly the side-specific cases this PR is fixing.

🛠️ Proposed fix
         switch Int(event.keyCode) {
         case Int(kVK_RightOption):
             if hasRightOptionBit {
                 inferredRightOptionDown = true
                 inferredFallbackToggleArmed = true
             } else if !optionDown {
                 inferredRightOptionDown = false
                 inferredFallbackToggleArmed = true
             } else if inferredFallbackToggleArmed {
-                inferredRightOptionDown.toggle()
+                inferredRightOptionDown = true
                 inferredFallbackToggleArmed = false
+            } else {
+                inferredRightOptionDown = false
+                inferredFallbackToggleArmed = true
             }
         case Int(kVK_Option):
             if hasRightOptionBit {
                 inferredRightOptionDown = true
                 inferredFallbackToggleArmed = true
@@
     private func isRightOptionActive(event: NSEvent, flags: NSEvent.ModifierFlags) -> Bool {
+        if flags.contains(Self.rightOptionModifierFlag) { return true }
         if event.type == .keyUp,
            Int(event.keyCode) == Int(kVK_RightOption),
            !flags.contains(.option) {
             return false
         }
         guard flags.contains(.option) else { return false }
-        if flags.contains(Self.rightOptionModifierFlag) { return true }
-        if Int(event.keyCode) == Int(kVK_RightOption) { return true }
+        if Int(event.keyCode) == Int(kVK_RightOption) { return rightOptionModifierDown }
         if Int(event.keyCode) == Int(kVK_Option) { return false }
         return rightOptionModifierDown
     }

Also applies to: 6274-6313

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

In `@Sources/GhosttyTerminalView.swift` around lines 6178 - 6211, The right-option
fallback handling leaves rightOptionModifierDown set when releasing the physical
right-Option key while the left Option remains pressed; update the switch logic
around Int(kVK_RightOption) and the default branch so that whenever optionDown
is false (no Option bits), inferredRightOptionDown is explicitly set to false
and inferredFallbackToggleArmed set to true — and ensure this state update
occurs before any early return that checks modifier flags later. Concretely,
adjust the kVK_RightOption case to also clear rightOptionModifierDown when
hasRightOptionBit is false and optionDown is false (even if previously armed),
and make the default branch mirror that same clearing behavior so
rightOptionModifierDown/rightOptionFallbackToggleArmed cannot desynchronize;
keep all changes confined to the variables rightOptionModifierDown,
rightOptionFallbackToggleArmed, inferredRightOptionDown, and
inferredFallbackToggleArmed to preserve the later modifier-flag logic.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@Sources/GhosttyTerminalView.swift`:
- Around line 6178-6211: The right-option fallback handling leaves
rightOptionModifierDown set when releasing the physical right-Option key while
the left Option remains pressed; update the switch logic around
Int(kVK_RightOption) and the default branch so that whenever optionDown is false
(no Option bits), inferredRightOptionDown is explicitly set to false and
inferredFallbackToggleArmed set to true — and ensure this state update occurs
before any early return that checks modifier flags later. Concretely, adjust the
kVK_RightOption case to also clear rightOptionModifierDown when
hasRightOptionBit is false and optionDown is false (even if previously armed),
and make the default branch mirror that same clearing behavior so
rightOptionModifierDown/rightOptionFallbackToggleArmed cannot desynchronize;
keep all changes confined to the variables rightOptionModifierDown,
rightOptionFallbackToggleArmed, inferredRightOptionDown, and
inferredFallbackToggleArmed to preserve the later modifier-flag logic.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 44bcee52-bab0-4d4f-98d2-625f721c8443

📥 Commits

Reviewing files that changed from the base of the PR and between 1856771 and 9d6acee.

📒 Files selected for processing (2)
  • Sources/GhosttyTerminalView.swift
  • cmuxTests/CJKIMEInputTests.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)
Sources/GhosttyTerminalView.swift (1)

6288-6313: ⚠️ Potential issue | 🟠 Major

consumed_mods can still miss ALT_RIGHT when translation strips .option.

Line 6290 currently checks right-option activity against the translated flags value. If Ghostty translation returns right-side intent without .option, isRightOptionActive(...) can evaluate false and drop GHOSTTY_MODS_ALT_RIGHT in consumed_mods.

Suggested fix
 private func consumedModsFromFlags(
     _ flags: NSEvent.ModifierFlags,
     sourceEvent: NSEvent? = nil
 ) -> ghostty_input_mods_e {
     var mods = GHOSTTY_MODS_NONE.rawValue
@@
     let rightOptionActive: Bool
     if let sourceEvent {
-        rightOptionActive = isRightOptionActive(event: sourceEvent, flags: flags)
+        // Use raw source-event flags for side detection; translation flags may
+        // intentionally drop `.option` while preserving right-side intent.
+        rightOptionActive = isRightOptionActive(event: sourceEvent, flags: sourceEvent.modifierFlags)
     } else {
         rightOptionActive = flags.contains(Self.rightOptionModifierFlag) || rightOptionModifierDown
     }
@@
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Sources/GhosttyTerminalView.swift` around lines 6288 - 6313, The
consumed_mods path can drop GHOSTTY_MODS_ALT_RIGHT because
isRightOptionActive(...) checks the translated flags parameter (which may have
.option stripped) instead of the event's original modifier flags; update the
logic to use the NSEvent's original modifier flags when testing for presence of
.option so right-side ALT intent isn't lost. Concretely, in
isRightOptionActive(event:flags:) replace checks that test
flags.contains(.option) with event.modifierFlags.contains(.option) (or change
the call to pass event.modifierFlags), and keep the other checks for
Self.rightOptionModifierFlag, kVK_RightOption/kVK_Option keyCode, and
rightOptionModifierDown so GHOSTTY_MODS_ALT_RIGHT is set reliably when computing
consumed_mods.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@Sources/GhosttyTerminalView.swift`:
- Around line 6288-6313: The consumed_mods path can drop GHOSTTY_MODS_ALT_RIGHT
because isRightOptionActive(...) checks the translated flags parameter (which
may have .option stripped) instead of the event's original modifier flags;
update the logic to use the NSEvent's original modifier flags when testing for
presence of .option so right-side ALT intent isn't lost. Concretely, in
isRightOptionActive(event:flags:) replace checks that test
flags.contains(.option) with event.modifierFlags.contains(.option) (or change
the call to pass event.modifierFlags), and keep the other checks for
Self.rightOptionModifierFlag, kVK_RightOption/kVK_Option keyCode, and
rightOptionModifierDown so GHOSTTY_MODS_ALT_RIGHT is set reliably when computing
consumed_mods.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: f8764374-baf1-4287-9e40-38034892137f

📥 Commits

Reviewing files that changed from the base of the PR and between 9d6acee and b7834cd.

📒 Files selected for processing (2)
  • Sources/GhosttyTerminalView.swift
  • cmuxTests/CJKIMEInputTests.swift
🚧 Files skipped from review as they are similar to previous changes (1)
  • cmuxTests/CJKIMEInputTests.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)
Sources/GhosttyTerminalView.swift (1)

7159-7169: ⚠️ Potential issue | 🟠 Major

Right-Option keyUp still reports ALT_RIGHT when left Option stays down.

Line 7167 re-marks any kVK_RightOption event as right-side active, so after flagsChanged(with:) clears rightOptionModifierDown for the ⌥R up while ⌥L remains held fallback case, modsFromEvent(_:) still emits ALT_RIGHT on the release event. That leaves the side-specific modifier state wrong for the exact transition this patch is trying to fix.

Suggested fix
 private func isRightOptionActive(event: NSEvent, flags: NSEvent.ModifierFlags) -> Bool {
-    if event.type == .keyUp,
-       Int(event.keyCode) == Int(kVK_RightOption),
-       !flags.contains(.option) {
+    if event.type == .keyUp,
+       Int(event.keyCode) == Int(kVK_RightOption) {
         return false
     }
     guard flags.contains(.option) else { return false }
     if flags.contains(Self.rightOptionModifierFlag) { return true }
-    if Int(event.keyCode) == Int(kVK_RightOption) { return true }
     if Int(event.keyCode) == Int(kVK_Option) { return false }
     return rightOptionModifierDown
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Sources/GhosttyTerminalView.swift` around lines 7159 - 7169, The
isRightOptionActive(_:flags:) helper wrongly treats any event with event.keyCode
== kVK_RightOption as right-side active even on keyUp when the left Option
remains held; update the logic so that for keyUp events with keyCode ==
kVK_RightOption you do not return true unconditionally but instead consult the
rightOptionModifierDown state (or explicitly return false when
rightOptionModifierDown is false) before marking right-side active. In short,
inside isRightOptionActive check event.type == .keyUp && Int(event.keyCode) ==
Int(kVK_RightOption) and return rightOptionModifierDown (or false) rather than
falling through to the unconditional `if Int(event.keyCode) ==
Int(kVK_RightOption) { return true }` branch so the release of ⌥R while ⌥L is
held does not emit ALT_RIGHT.
🧹 Nitpick comments (2)
cmuxTests/CJKIMEInputTests.swift (2)

1833-1839: Minor: Character doesn't match key code for Option+D.

Line 1834 uses characters: "˚" with keyCode: kVK_ANSI_D, but "˚" is produced by Option+K on a US keyboard, not Option+D (which produces "∂" as used in line 1751). This doesn't affect test correctness since the test validates modifier flags, but for consistency:

Suggested fix
         let sent = hostedView.debugSendSyntheticKeyPressAndReleaseForUITest(
-            characters: "˚",
+            characters: "∂",
             charactersIgnoringModifiers: "d",
             keyCode: UInt16(kVK_ANSI_D),
             modifierFlags: [.option]
         )
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@cmuxTests/CJKIMEInputTests.swift` around lines 1833 - 1839, The test uses
hostedView.debugSendSyntheticKeyPressAndReleaseForUITest with characters: "˚"
but keyCode: UInt16(kVK_ANSI_D) and modifierFlags: [.option]; update the
characters argument to match the character produced by Option+D on a US keyboard
(use "∂") so the characters value is consistent with the keyCode kVK_ANSI_D and
modifierFlags in the call to debugSendSyntheticKeyPressAndReleaseForUITest.

1736-1738: Misleading variable name: rightOptionOnly contains no right-Option indicator.

The variable rightOptionOnly is set to [.option] without the device-specific right-Option bit. The right-Option state is actually injected via debugSetRightOptionModifierDownForUITest(true). Consider renaming for clarity:

Suggested rename
-        let rightOptionOnly = NSEvent.ModifierFlags([.option])
-        hostedView.debugSetRightOptionModifierDownForUITest(true)
+        let optionFlagsWithoutDeviceBit = NSEvent.ModifierFlags([.option])
+        hostedView.debugSetRightOptionModifierDownForUITest(true)

Then update the reference at line 1754:

-            modifierFlags: rightOptionOnly
+            modifierFlags: optionFlagsWithoutDeviceBit
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@cmuxTests/CJKIMEInputTests.swift` around lines 1736 - 1738, Rename the
misleading variable rightOptionOnly (an NSEvent.ModifierFlags([.option]) value)
to something that does not imply device-specific right-Option (e.g.,
optionFlagOnly or optionModifier) and update its usage; note the right-Option
device bit is actually set by
hostedView.debugSetRightOptionModifierDownForUITest(true), so leave that call
as-is but change the variable name wherever referenced (search for
rightOptionOnly and replace with the new name) to make intent clear.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@Sources/GhosttyTerminalView.swift`:
- Around line 7159-7169: The isRightOptionActive(_:flags:) helper wrongly treats
any event with event.keyCode == kVK_RightOption as right-side active even on
keyUp when the left Option remains held; update the logic so that for keyUp
events with keyCode == kVK_RightOption you do not return true unconditionally
but instead consult the rightOptionModifierDown state (or explicitly return
false when rightOptionModifierDown is false) before marking right-side active.
In short, inside isRightOptionActive check event.type == .keyUp &&
Int(event.keyCode) == Int(kVK_RightOption) and return rightOptionModifierDown
(or false) rather than falling through to the unconditional `if
Int(event.keyCode) == Int(kVK_RightOption) { return true }` branch so the
release of ⌥R while ⌥L is held does not emit ALT_RIGHT.

---

Nitpick comments:
In `@cmuxTests/CJKIMEInputTests.swift`:
- Around line 1833-1839: The test uses
hostedView.debugSendSyntheticKeyPressAndReleaseForUITest with characters: "˚"
but keyCode: UInt16(kVK_ANSI_D) and modifierFlags: [.option]; update the
characters argument to match the character produced by Option+D on a US keyboard
(use "∂") so the characters value is consistent with the keyCode kVK_ANSI_D and
modifierFlags in the call to debugSendSyntheticKeyPressAndReleaseForUITest.
- Around line 1736-1738: Rename the misleading variable rightOptionOnly (an
NSEvent.ModifierFlags([.option]) value) to something that does not imply
device-specific right-Option (e.g., optionFlagOnly or optionModifier) and update
its usage; note the right-Option device bit is actually set by
hostedView.debugSetRightOptionModifierDownForUITest(true), so leave that call
as-is but change the variable name wherever referenced (search for
rightOptionOnly and replace with the new name) to make intent clear.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 5a319927-e1a7-4ee7-81cc-471383098d93

📥 Commits

Reviewing files that changed from the base of the PR and between b7834cd and dd5ae09.

📒 Files selected for processing (2)
  • Sources/GhosttyTerminalView.swift
  • cmuxTests/CJKIMEInputTests.swift

@JannikWempe

Copy link
Copy Markdown

Is there anything missing here? This is currently preventing me to fully migrate from ghostty to cmux.

Thanks for your work!

@lejahmie

Copy link
Copy Markdown
Author

Is there anything missing here? This is currently preventing me to fully migrate from ghostty to cmux.

Thanks for your work!
Everything works, but need permissions to run final build steps, and approvals to merge of course.

I have been waiting for weeks, and have tried to reach out to the maintainers for weeks now. I guess they are busy people. 😢

@lejahmie

lejahmie commented May 1, 2026 •

Copy link
Copy Markdown
Author

Is there anything missing here? This is currently preventing me to fully migrate from ghostty to cmux.

Thanks for your work!

@JannikWempe fuck it, built my own from scratch.... you are welcome to beta test it :-D
https://github.com/finger-gun/omux/
https://openmux.fingergun.dev/

@Deeptwix

Copy link
Copy Markdown

Bumping this because #2369 makes it tricky to use cmux for most things. This PR would be quite helpful.

I'm game if there's anything I can do to help getting it merged.

@teamleaderleo

Copy link
Copy Markdown
Collaborator

Shipped on main, including side-specific Option handling and regression coverage: 558d6b9.

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.

macos-option-as-alt = right` not respected in cmux (works in Ghostty) — breaks ISO keyboard usability

5 participants