Skip to content

Fix Ghostty keybindings for workspace navigation (#135) - #4027

Closed
austinywang wants to merge 10 commits into
mainfrom
issue-135-keybindings-ghostty
Closed

austinywang wants to merge 10 commits into
mainfrom
issue-135-keybindings-ghostty

Conversation

@austinywang

@austinywang austinywang commented May 12, 2026 •

Copy link
Copy Markdown
Contributor

Closes #135

Summary

  • route Ghostty previous_tab/next_tab keybinds to cmux workspace navigation
  • support Page Up/Page Down in shortcut recording, cmux.json parsing, matching, and schema docs
  • add regression coverage for super+alt+arrow and ctrl+page_up/page_down Ghostty configs

Tests

  • Not run locally per repository policy; CI will run the regression coverage.

Note

Medium Risk
Changes global shortcut routing and Ghostty config integration; conflict rules with surface navigation must stay correct to avoid surprising tab/surface behavior.

Overview
Ghostty previous_tab / next_tab keybinds from the Ghostty config now drive cmux workspace prev/next tab when they do not collide with cmux’s own surface shortcuts (Cmd+Shift+[/]). Shortcut reload is renamed to refreshGhosttyNavigationShortcuts, loads those two triggers, maps Page Up/Down from Ghostty physical keys (with key codes 116/121), and refreshes on the MainActor.

Page Up and Page Down are supported end-to-end in the shortcut stack: recording, parsing, display, direct key-code matching, cmux.schema.json, and localized labels. Regression tests cover Ghostty tab routing (Cmd+Opt+arrows, Ctrl+Page Up/Down) and page-key parsing/recording.

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


Summary by cubic

Routes Ghostty previous_tab/next_tab keybindings to cmux workspace navigation and adds first-class Page Up/Down support and labels. Fixes #135; keeps cmux surface shortcuts (Cmd+Shift+[/]) authoritative and runs Ghostty config reload/observers on the MainActor.

  • Bug Fixes

    • Route Ghostty tab navigation (Cmd+Opt+←/→, Ctrl+PageUp/PageDown) to previous/next workspace without overriding cmux surface navigation.
    • Run Ghostty config reload and shortcut refresh on the MainActor; tighten shortcut matching to avoid edge cases.
    • Localize Page Up/Down labels across locales and order entries.
  • New Features

    • Add Page Up/Down key support: recording, parsing/normalization, direct keyCode matching (116/121), display strings, and web/data/cmux.schema.json.
    • Add regression tests for Ghostty tab routing and Page Up/Down shortcut round-trips.

Written for commit 3da86d7. Summary will update on new commits. Review in cubic

Summary by CodeRabbit

  • New Features

    • Added Page Up / Page Down as supported keys for custom shortcuts and improved config parsing/normalization for their multiple spellings.
    • Improved tab/workspace navigation to honor external keyboard bindings when appropriate.
  • Tests

    • Added tests for Page Up/Page Down parsing, recording, matching, and for external keybind routing that affects workspace navigation.
  • Localization

    • Added Page Up / Page Down labels (including English and Japanese).

Review Change Stack

@vercel

vercel Bot commented May 12, 2026 •

Copy link
Copy Markdown

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

Project Deployment Actions Updated (UTC)
cmux Ready Ready Preview, Comment May 26, 2026 9:20pm
cmux-staging Building Building Preview, Comment May 26, 2026 9:20pm

@coderabbitai

coderabbitai Bot commented May 12, 2026 •

Copy link
Copy Markdown
📝 Walkthrough

Walkthrough

Adds Page Up/Page Down as canonical shortcut keys (parsing, key-code mapping, localization, and schema), extends AppDelegate to read and store Ghostty previous/next tab triggers, routes Ghostty tab-navigation into workspace selection while preserving cmux surface-navigation authority, and adds unit/integration tests for parsing, recording, and Ghostty routing.

Changes

Page Up/Page Down and Ghostty Tab Navigation

Layer / File(s) Summary
Shortcut parsing, key-code mapping, schema, and localization
Sources/KeyboardShortcutSettings.swift, web/data/cmux.schema.json, Resources/Localizable.xcstrings
Accepts multiple spellings for PageUp/PageDown, normalizes to pageUp/pageDown, maps hardware key codes 116/121, adds localized display strings, and updates JSON schema to include page keys.
Recording and matching tests for Page keys
cmuxTests/KeyboardShortcutSpaceKeyTests.swift, cmuxTests/WorkspaceUnitTests.swift, cmuxTests/AppDelegateShortcutRoutingTests.swift
Tests validate config parsing round-trips, recording accepts Page Up events with Control+Function modifiers, and matching uses NSPageUp/NSPageDown function-event characters.
Ghostty navigation shortcut storage and refresh
Sources/AppDelegate.swift
Adds ghosttyPreviousTabShortcut/ghosttyNextTabShortcut, replaces startup/config-reload refresh calls with refreshGhosttyNavigationShortcuts() to populate or clear navigation-related stored shortcuts.
Ghostty trigger extraction and PageUp/PageDown mapping
Sources/AppDelegate.swift
Maps Ghostty physical PageUp/PageDown triggers to pageUp/pageDown StoredShortcut representations and sets StoredShortcut.keyCode for those physical triggers.
Workspace navigation routing and suppression
Sources/AppDelegate.swift
Updates .nextSidebarTab / .prevSidebarTab handling to route Ghostty tab-navigation triggers via shouldRouteGhosttyTabNavigationShortcut(...), which suppresses Ghostty when cmux surface-navigation should remain authoritative.
Ghostty test helpers and integration tests
cmuxTests/AppDelegateShortcutRoutingTests.swift
Adds helpers to write temporary Ghostty configs and tests verifying Ghostty previous_tab/next_tab routing via Cmd+Option+arrow and Ctrl+PageUp/PageDown changes workspace selection.

🎯 4 (Complex) | ⏱️ ~45 minutes

Suggested reviewers

  • jesstelford

Poem

🐰 I nibble keys with whiskered cheer,
Page Up and Down hop bright and clear,
Ghostty and cmux now step in time,
Tabs glide softly, no tug, no crime,
A tiny rabbit claps—shortcuts align.


Important

Pre-merge checks failed

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

❌ Failed checks (1 warning, 2 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Cmux Swift Actor Isolation ❓ Inconclusive No result was produced after verification. Marking as INCONCLUSIVE. Re-run the check or adjust instructions to produce a final result.
Description check ❓ Inconclusive The pull request description covers the main changes (Ghostty tab routing, Page Up/Down support) and testing approach, but is missing some required template sections. Add a demo video link (or note why it's not applicable), confirm local testing status, and clarify which tests were added or updated to match the testing checklist.
✅ Passed checks (14 passed)
Check name Status Explanation
Title check ✅ Passed The title 'Fix Ghostty keybindings for workspace navigation (#135)' accurately describes the main change: routing Ghostty keybindings to workspace navigation and resolving the issue.
Linked Issues check ✅ Passed The PR successfully addresses all coding requirements from issue #135: routes Ghostty previous_tab/next_tab to workspace navigation, adds Page Up/Down support, preserves cmux surface shortcuts as authoritative, and includes regression tests.
Out of Scope Changes check ✅ Passed All changes directly support the PR objectives: Ghostty keybinding routing, Page Up/Down support across the shortcut system, MainActor safety, and regression tests. No out-of-scope changes detected.
Cmux Swift Blocking Runtime ✅ Passed No blocking synchronization primitives (semaphores, sleeps, locks, or DispatchQueue.main.sync) found in new production code; MainActor.assumeIsolated used appropriately for actor safety.
Cmux No Hacky Sleeps ✅ Passed PR contains only Swift code, test code, localization strings, and JSON schema—no TypeScript/JavaScript/shell/build-runtime script changes subject to this rule.
Cmux Swift Concurrency ✅ Passed PR avoids legacy async patterns. MainActor.assumeIsolated usage is correct Swift concurrency (not legacy) used in main-thread contexts.
Cmux Swift @Concurrent ✅ Passed All new Swift functions are synchronous and properly @MainActor-isolated. No @concurrent annotations added (correct). Calls wrapped in MainActor.assumeIsolated{}.
Cmux Swift File And Package Boundaries ✅ Passed Test additions allowed; AppDelegate adds 72 lines of Ghostty-glue (under 250 threshold); KeyboardShortcutSettings adds 18 lines without mixing responsibilities.
Cmux Swift Logging ✅ Passed All production code changes comply with Swift logging rules: no print/debugPrint/dump/NSLog in runtime code; debug logging guarded by #if DEBUG; no ad hoc file/stdout logging or secrets exposure.
Cmux User-Facing Error Privacy ✅ Passed PR adds localization for generic keyboard keys (Page Up/Down) and Ghostty config routing. No user-facing errors, alerts, or vendor names exposed.
Cmux Full Internationalization ✅ Passed New user-facing Swift keys use String(localized:) with proper defaultValue; shortcut.key.pageUp/Down entries in Localizable.xcstrings include complete translations for all supported locales.
Cmux Swiftui State Layout ✅ Passed PR contains no SwiftUI state changes. Changes are to AppKit AppDelegate and value-type structs with no @Observable/@Published/@StateObject patterns.
Cmux Architecture Rethink ✅ Passed Observer caches Ghostty shortcuts from GhosttyApp.shared.config via established notification, synced on startup and reload with MainActor platform bridge, following existing codebase patterns.
Cmux Swift Auxiliary Window Close Shortcuts ✅ Passed PR modifies keyboard shortcut routing and Page Up/Down support in existing main window, with no new user-visible NSWindow, NSPanel, NSWindowController, or SwiftUI Window/WindowGroup declarations.
✨ 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 issue-135-keybindings-ghostty

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 May 12, 2026 •

Copy link
Copy Markdown
Contributor

Greptile Summary

Routes Ghostty previous_tab/next_tab keybindings to cmux workspace navigation, adds full Page Up/Down support across the shortcut system (recording, parsing, matching, schema, and localization), and guards against Ghostty's macOS defaults stealing cmux surface-navigation shortcuts.

  • Ghostty tab routing: refreshGhosttyNavigationShortcuts() now reads previous_tab/next_tab triggers from the Ghostty config and stores them as StoredShortcut; shouldRouteGhosttyTabNavigationShortcut conditionally routes matching events to workspace navigation while keeping the configured cmux surface shortcut (e.g. Cmd+Shift+[/]) authoritative.
  • Page Up/Down support: Key codes 116/121 are wired into storedKey, keyCodeForShortcutKey, usesDirectKeyCodeMatching, parseConfigKeyToken, and the JSON schema pattern; localization entries are added for all 19 supported locales, resolving the earlier P1 from the previous review cycle.
  • Observer bridging: The Ghostty-config-reload notification observer and the startup call to refreshGhosttyNavigationShortcuts both use MainActor.assumeIsolated; regression tests cover Ghostty arrow-key and Ctrl+Page Up/Down routing.

Confidence Score: 5/5

Safe to merge; the routing guard correctly prevents Ghostty defaults from stealing cmux surface shortcuts, the page key matching path is consistent across all call sites, and all 19 locales are covered.

The change is well-scoped: new shortcut slots are wired through every layer (recording, parsing, key-code lookup, direct-match flag, schema, and localization) in a consistent way, regression tests cover both the arrow-key and page-key routing paths, and the surface-navigation guard correctly preserves cmux priority.

Sources/AppDelegate.swift — the startup call to refreshGhosttyNavigationShortcuts is wrapped in MainActor.assumeIsolated unnecessarily; cmuxTests/AppDelegateShortcutRoutingTests.swift — the new withTemporaryGhosttyConfig helper uses process-global setenv with a 50 ms RunLoop gate.

Important Files Changed

Filename Overview
Sources/AppDelegate.swift Adds ghosttyPreviousTabShortcut/ghosttyNextTabShortcut properties, extends refreshGhosttyNavigationShortcuts to populate them, adds shouldRouteGhosttyTabNavigationShortcut guard, and wraps startup/observer calls in MainActor.assumeIsolated — which is redundant at the startup call site since AppDelegate is already @mainactor.
Sources/KeyboardShortcutSettings.swift Correctly wires Page Up/Down through storedKey (key-code to string), keyCodeForShortcutKey (string to key-code, lowercase), usesDirectKeyCodeMatching (now lowercases before comparison), and parseConfigKeyToken (normalizes all variant spellings to camelCase). Logic is consistent across all call paths.
cmuxTests/AppDelegateShortcutRoutingTests.swift Adds withTemporaryGhosttyConfig helper that mutates process-global environment variables and drives a singleton GhosttyApp reload, then waits with two RunLoop.main.run(until:) timing gates; pattern is timing-sensitive.
cmuxTests/KeyboardShortcutSpaceKeyTests.swift Adds thorough round-trip tests for ctrl+page_up/ctrl+page_down: config parsing, key normalization, configIdentifier, resolvedKeyCode, and the matches() predicate.
cmuxTests/WorkspaceUnitTests.swift Adds testShortcutRecordingResultAcceptsPageKeys exercising the key-recording path for Ctrl+PageUp via a synthetic NSEvent.
Resources/Localizable.xcstrings Adds shortcut.key.pageUp and shortcut.key.pageDown with translations for all 19 supported locales, resolving the previous review's P1 about missing locales.
web/data/cmux.schema.json Extends shortcutStroke regex pattern with all four page-key spellings and updates the description string accordingly.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[NSEvent keyDown] --> B{matchConfiguredShortcut nextSidebarTab or prevSidebarTab?}
    B -- yes --> C[Route to workspace nav]
    B -- no --> D{shouldRouteGhosttyTabNavShortcut?}
    D --> E{matchShortcut with ghosttyNextTab or ghosttyPrevTab?}
    E -- no or shortcut nil --> F[Pass event through]
    E -- yes --> G{matchConfiguredShortcut nextSurface or prevSurface?}
    G -- yes cmux owns it --> F
    G -- no --> C
    subgraph Boot and reload
        H[App launch or ghosttyConfigDidReload] --> I[refreshGhosttyNavigationShortcuts]
        I --> J[storedShortcutFromGhosttyTrigger for each goto_split and tab action]
    end
Loading

Reviews (6): Last reviewed commit: "fix: localize page key labels" | Re-trigger Greptile

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 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 `@Resources/Localizable.xcstrings`:
- Around line 83436-83441: Update the Japanese localization entries for the
keyboard shortcuts shortcut.key.pageUp and shortcut.key.pageDown by replacing
the English values "Page Up" and "Page Down" in the "ja" stringUnit.value fields
with the correct Japanese translations "ページアップ" and "ページダウン" respectively so
they match the other translated key names in the localization file.
🪄 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: 59ceeab9-042e-4bfd-bd6f-9bef249b69d7

📥 Commits

Reviewing files that changed from the base of the PR and between 5829da2 and 3af7ef9.

📒 Files selected for processing (7)
  • Resources/Localizable.xcstrings
  • Sources/AppDelegate.swift
  • Sources/KeyboardShortcutSettings.swift
  • cmuxTests/AppDelegateShortcutRoutingTests.swift
  • cmuxTests/KeyboardShortcutSpaceKeyTests.swift
  • cmuxTests/WorkspaceUnitTests.swift
  • web/data/cmux.schema.json

Comment thread cmuxTests/AppDelegateShortcutRoutingTests.swift
Comment thread Resources/Localizable.xcstrings Outdated
Comment thread Resources/Localizable.xcstrings

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

Comment thread Sources/KeyboardShortcutSettings.swift

This branch was successfully deployed

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

Labels

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Customizable keybindings / respect Ghostty hotkey config

3 participants