Keyboard Shortcuts
LeapMux ships with a VS Code-style keyboard shortcut system: every shortcut is a command bound to a key, and each binding is active only inside a context (for example, only when a dialog is open or a terminal is focused). This chapter explains how the system works, lists every default binding, and documents the override format the engine uses for customization.
How shortcuts work
A keyboard shortcut in LeapMux is the combination of three independent pieces:
- Command — a named action with a human title, such as
app.newAgent(“New Agent”) orapp.closeActiveTab(“Close Active Tab”). The command holds the logic; it does not know which key triggers it. - Keybinding — a key string mapped to a command, optionally restricted by a
when-clause. For example,$mod+nrunsapp.newAgentwhen!dialogOpen(no dialog is open). - Context — a set of named boolean/string values evaluated the moment you press a key (for example
dialogOpen,terminalFocused, orplatform == "mac"). A binding fires only if itswhen-clause evaluates to true against the current context.
Because these are decoupled, the same command can be bound to several keys, and the same key can run different commands depending on what is focused.
The $mod key
Key strings use the tinykeys vocabulary. The most important token is $mod:
- On macOS,
$modmeans Cmd (⌘). - On Windows and Linux,
$modmeans Ctrl.
So a single binding such as $mod+n is shown as ⌘N on a Mac and Ctrl+N everywhere else. The tables below give both forms.
Other modifiers are Shift, Alt (also written Option), Control/Ctrl, and Meta. Named keys used in the defaults include F5, F12, the arrow keys, PageUp/PageDown, the bracket and backslash keys, and the digits 0–9.
Chords
The engine supports chords — space-separated key sequences such as $mod+k $mod+s (press the first combination, then the second). Chords render correctly in tooltips and menus, but no default binding uses one. The override format supports chords (see Customizing keybindings), with one caveat on the desktop app (see Desktop (Tauri) accelerator differences).
When shortcuts are suppressed
LeapMux binds shortcuts at the window level so they work no matter where focus sits, but it suppresses some of them to avoid hijacking your typing:
- Input focus. When the cursor is in a text input, textarea, select, or any
contenteditableelement, a shortcut with no modifier that is not a function key is suppressed — unless itswhen-clause explicitly refers toinputFocused. The suppression does not apply to shortcuts that use a modifier ($mod,Ctrl,Alt,Meta,Shift) or a plain function key (F1–F12): those always fire, regardless of focus. - IME composition. While you are composing text with an Input Method Editor (for example, typing Chinese, Japanese, or Korean), shortcuts are held back so they cannot interfere with your text entry.
Contexts (the when clause)
A binding’s when-clause is a small boolean expression evaluated against the active context. The available context values are:
| Context | Type | Meaning |
|---|---|---|
isDesktop | boolean | Running inside the Tauri desktop app. |
platform | string | "mac", "windows", or "linux". |
dialogOpen | boolean | A modal dialog is currently open. |
sidebarVisible | boolean | The left sidebar is expanded. |
activeTabType | string | "agent", "terminal", "file", "image", or empty when no tab is active. |
inputFocused | boolean | Focus is in an input, textarea, select, or contenteditable element. |
editorFocused | boolean | Focus is inside a rich-text editor surface. |
chatInputFocused | boolean | Focus is inside a chat message input. |
chatInputEmpty | boolean | The current agent tab’s composer has nothing to submit: no text, no attachment, and no permission prompt an empty submit would answer. |
terminalFocused | boolean | Focus is inside a terminal. |
The when expression grammar supports || (or), && (and), ! (not), parentheses, ==/!= comparisons, identifiers, quoted strings, and the literals true/false. An empty or absent when means “always active.” Examples drawn from the defaults: !dialogOpen, isDesktop, !dialogOpen && isDesktop, activeTabType == "agent" && !dialogOpen, and terminalFocused && platform == "mac".
Default keyboard shortcuts
The tables below list every default binding. The macOS column uses Apple’s symbol glyphs in HIG order (⌃⌥⇧⌘, run together with no separators); the Windows / Linux column uses Ctrl/Alt/Shift joined with +. Where a command has more than one binding, both are shown separated by / — exactly as LeapMux renders them in tooltips and menus.
App
| Command | macOS | Windows / Linux | Active when |
|---|---|---|---|
| Open Preferences | ⌘, | Ctrl+, | always |
Open Preferences opens the Preferences dialog. It is a workspace binding (it works everywhere inside a workspace), not desktop-only. On the macOS desktop app it is also mirrored onto the native menu bar — see Desktop (Tauri) accelerator differences. For the dialog’s contents, see Settings & Preferences.
Tabs
| Command | macOS | Windows / Linux | Active when |
|---|---|---|---|
| New Agent | ⌘N | Ctrl+N | no dialog open |
| New Terminal | ⌘T | Ctrl+T | no dialog open |
| Close Active Tab | ⌘W | Ctrl+W | no dialog open |
| New Agent Dialog | ⇧⌘N | Ctrl+Shift+N | no dialog open |
| New Terminal Dialog | ⇧⌘T | Ctrl+Shift+T | no dialog open |
| Toggle Floating Tab | ⇧⌘O | Ctrl+Shift+O | no dialog open |
| Switch to Tab 1–9 | ⌘1–⌘9 / ⌥1–⌥9 | Ctrl+1–Ctrl+9 / Alt+1–Alt+9 | always |
| Previous Tab | ⌘[ / ⌘PageUp | Ctrl+[ / Ctrl+PageUp | always |
| Next Tab | ⌘] / ⌘PageDown | Ctrl+] / Ctrl+PageDown | always |
Tab switching and navigation operate on the visible tabs of the focused tile (or on all tabs when no tile is focused). Previous/Next wrap around and do nothing if a tile has fewer than two tabs. See Tabs & Layout.
Layout
| Command | macOS | Windows / Linux | Active when |
|---|---|---|---|
| Split Tile Horizontally | ⌘\ | Ctrl+\ | always |
| Split Tile Vertically | ⇧⌘\ | Ctrl+Shift+\ | always |
| Toggle Left Sidebar | ⇧⌘[ | Ctrl+Shift+[ | always |
| Toggle Right Sidebar | ⇧⌘] | Ctrl+Shift+] | always |
Splitting acts on the focused tile. For more on tiling and sidebars, see Tabs & Layout.
View
| Command | macOS | Windows / Linux | Active when |
|---|---|---|---|
| Scroll Active Tab Up One Page | ⌥PageUp | Alt+PageUp | no dialog open |
| Scroll Active Tab Down One Page | ⌥PageDown | Alt+PageDown | no dialog open |
Page scrolling only acts when the active tab is an agent or a terminal.
Files
| Command | macOS | Windows / Linux | Active when |
|---|---|---|---|
| Refresh Directory Tree | ⌘R / F5 | Ctrl+R / F5 | always |
| Toggle Hidden Files | ⇧⌘H | Ctrl+Shift+H | always |
These act on the file browser. See File Browser.
Chat
| Command | macOS | Windows / Linux | Active when |
|---|---|---|---|
| Steer Queued Input | ⌘⏎ | Ctrl+⏎ | agent tab, composer empty, no terminal focused, no dialog open |
Steer Queued Input hands the input queue’s first item to the turn the agent already runs. It acts only on an empty composer, because the same chord sends whatever the composer holds — so text, an attachment, or a permission prompt awaiting approval all send as usual. Nothing happens when the provider does not accept a steer, when the queue is empty, or when the first item is not in a steerable state. See The input queue.
Send Message (command chat.sendMessage) submits the focused chat input from anywhere inside the chat panel. It has no default chord, because the chat editor’s own Enter and Cmd/Ctrl+Enter already send — see Chat editor keys below. Bind it in Preferences if you want a second way in.Terminal
| Command | macOS | Windows / Linux | Active when |
|---|---|---|---|
| Toggle Quake Terminal | ⌃` | Ctrl+` | no dialog open |
The Quake terminal is a shell that slides over the centre of the app for one working directory, and the chord works in every tab kind because every tab has one; see Quake-mode terminal. It uses Ctrl on macOS too, because macOS reserves Cmd and that key for its own window cycler. The binding follows the key’s POSITION, so it is the key under Esc whatever character your layout prints on it. Open Quake Terminal (terminal.openQuake) and Close Quake Terminal (terminal.closeQuake) have no default chord and are there to bind if you would rather not toggle.
Terminal (macOS only)
These give Mac users emacs-style line and word motion under Cmd/Option + arrow keys. They are active only when a terminal is focused and the platform is macOS.
| Command | macOS | Active when |
|---|---|---|
| Go to Line Start | ⌘← | terminal focused, macOS |
| Go to Line End | ⌘→ | terminal focused, macOS |
| Go to Previous Word | ⌥← | terminal focused, macOS |
| Go to Next Word | ⌥→ | terminal focused, macOS |
Under the hood these send terminal control sequences (Ctrl-A, Ctrl-E, Esc-b, Esc-f) to the focused terminal. See Terminals.
Dialogs
Esc closes the most recently opened dialog — unless that dialog is busy (for example, mid-operation), in which case it resists closing until the operation finishes.
Esc is not a keybinding, and no command performs this action. The browser delivers the key to the innermost open layer, so one press dismisses one layer: with a dropdown menu open inside a dialog, the first Esc closes the menu and the second closes the dialog. A control that answers Esc itself comes first in the same way — the Preferences search box clears the query, and an inline rename cancels the edit.
Esc is reserved: an override that binds it is ignored, and the log records the entry that was dropped. Every keybinding fires from a single listener that runs ahead of the layers, so a binding for Esc would close a dialog underneath an open menu — which is the behaviour the reservation exists to prevent.
New Workspace
| Command | macOS | Windows / Linux | Active when |
|---|---|---|---|
| New Workspace Dialog | ⌥⌘N | Ctrl+Alt+N | no dialog open |
See Workspaces.
Desktop app only
The isDesktop clause restricts these to the desktop app. They have no effect in the browser.
| Command | macOS | Windows / Linux | Active when |
|---|---|---|---|
| Open in External App | ⇧⌘E | Ctrl+Shift+E | no dialog open, desktop |
| Open Web Inspector | ⌥⌘I / F12 | Ctrl+Alt+I / F12 | desktop |
| Zoom In | ⌘= / ⌘Num+ | Ctrl+= / Ctrl+Num+ | desktop |
| Zoom Out | ⌘- / ⌘Num- | Ctrl+- / Ctrl+Num- | desktop |
| Actual Size | ⌘0 / ⌘Num0 | Ctrl+0 / Ctrl+Num0 | desktop |
| Quit Application | ⌘Q | Ctrl+Q | desktop |
The zoom, web-inspector, and quit shortcuts (“core” bindings) are mounted at the application root, so they work on every screen — including the launcher and the sign-in/setup pages — not just inside a workspace.
Open in external app (desktop, solo mode)
The ⇧⌘E / Ctrl+Shift+E shortcut opens the active tab’s working directory in an external application. It is one face of a feature that also lives in the desktop workspace title bar as a split button, and in the sidebar’s workspace, repository and branch row menus.
Where it appears. The split button shows in the title bar only when all three conditions hold. You are on the desktop app in solo mode. The active tab has a working directory. The machine reports at least one application. It is hidden in the browser and in distributed mode.
The two faces of the split button.
- The main face reads “Open in {AppName}” (with the application’s icon) when you have a preferred application. It reads “Open in …” with a generic icon when you have not picked one. With a preferred application, clicking the main face — or pressing
⇧⌘E/Ctrl+Shift+E— launches it. Without one, clicking the main face opens the dropdown; pressing the shortcut launches the first detected EDITOR and remembers it. It skips the file manager, which leads the list, because a machine with three editors installed should not open Finder. - The chevron opens a dropdown that lists your file manager first, then every detected editor alphabetically, with a checkmark on the current preferred one. A separator and “Refresh app list” close the list.
Picking a row from the dropdown opens the working directory in that application and remembers it. The main face and the keyboard shortcut then follow your last pick. “Refresh app list” re-probes your machine, which is what you want after you install or remove an application.
The file manager is one of the choices. Pick Finder, File Explorer or your Linux file manager and the split button opens the directory there. The row menus that offer the same list already carry Reveal in file manager of their own, so they drop their “Open in …” row while the file manager is your preferred application.
The same list in the sidebar. A workspace row, a repository row and a branch row each carry a Repository section with Copy repository URL, Copy repository path, Reveal in file manager, an Open in {AppName} row for your preferred application, and an Open in… submenu holding the whole list. A workspace or a repository that spans more than one checkout gives each checkout its own submenu, so one checkout’s actions stay together.
What gets opened. LeapMux opens the active tab’s working directory, or the checkout a row menu names. The directory must be absolute and must exist. LeapMux rejects relative paths, missing paths, and plain files, so nothing launches when the active tab has no real directory.
Detected applications. LeapMux auto-detects popular editors including VS Code, the JetBrains IDEs, Cursor, Zed, Sublime Text, and others. It probes known install locations, your PATH, and the JetBrains Toolbox directory. On macOS it prefers the installed .app bundle, because that is the route that brings the application’s window to the front. Your operating system’s file manager is always in the list. The chevron dropdown shows exactly what was found on your machine.
Reading the macOS glyphs
| Glyph | Modifier |
|---|---|
| ⌘ | Cmd ($mod on macOS) |
| ⌃ | Control |
| ⌥ | Option / Alt |
| ⇧ | Shift |
A few key names also render specially: Escape shows as Esc, Enter as ⏎, arrows as ← → ↑ ↓, NumpadAdd as Num+, NumpadSubtract as Num-, and Numpad0 as Num0. On macOS, modifiers always appear in the order ⌃⌥⇧⌘ with no separators, so Cmd renders last (for example, ⇧⌘N). On Windows and Linux they are joined with + (for example, Ctrl+Shift+N).
Customizing keybindings
Customize bindings in the Preferences → Keyboard Shortcuts category: a table of every command with its binding and source (Default or Custom). Click a binding to capture a new chord; a chord already bound in the same context is refused with the conflicting command’s name; Reset restores the default.
Overrides are stored account-level (the keybindings user setting, capped at 200 entries), so they follow you to every device — see Settings & Preferences. The override format below is what the editor writes.
LeapMux’s shortcut engine is built to rebind, add, or remove shortcuts through account-level overrides stored as JSON. The rest of this section describes that format and how the engine merges it with the defaults.
Where overrides live
Overrides live in your account settings on the Hub (the keybindings key), not in browser storage, so they follow you across browsers and devices when you sign in to the same account. The value is a JSON-encoded array of override objects:
[
{ "command": "app.newAgent", "key": "$mod+Shift+a" },
{ "command": "app.closeActiveTab", "key": "" },
{ "command": "app.toggleHiddenFiles", "key": "$mod+k $mod+h" }
]Each override has the shape:
| Field | Required | Meaning |
|---|---|---|
command | yes | The command id to bind (for example app.newAgent). |
key | yes | A tinykeys key string, or "" (empty) to unbind. |
when | no | A when-clause; inherits the default’s clause if omitted. |
How overrides merge with the defaults
For each overridden command, LeapMux merges the overrides with the defaults using these rules:
- The default binding(s) for that command are replaced by all non-empty-key overrides for it.
- Each override inherits the default’s
when-clause unless it specifies its ownwhen. - A command may have multiple overrides — it gets bound to every non-empty key listed.
- An override with
"key": ""(empty) unbinds the command. If every override for a command has an empty key, the command is fully unbound. - An override for a command not in the defaults is appended as a brand-new binding (with no inherited
when-clause). - An override whose key is
Escapeis dropped, and the command keeps whatever the defaults give it.Escis reserved for the innermost open layer — see Dialogs. A chord that merely contains it, such asShift+Escape, binds normally.
Changes take effect immediately: when the overrides change, LeapMux re-merges and re-binds the workspace shortcuts without a reload.
Each override targets a command by its id. Each id corresponds to one of the actions in the default tables above; the bindable workspace command ids are:
| Command id | Action |
|---|---|
app.newAgent | New Agent |
app.newTerminal | New Terminal |
app.closeActiveTab | Close Active Tab |
app.newAgentDialog | New Agent Dialog |
app.newTerminalDialog | New Terminal Dialog |
app.newWorkspaceDialog | New Workspace Dialog |
app.toggleFloatingTab | Toggle Floating Tab |
app.refreshDirectoryTree | Refresh Directory Tree |
app.toggleHiddenFiles | Toggle Hidden Files |
app.switchToTab1 … app.switchToTab9 | Switch to Tab 1–9 |
app.previousTab / app.nextTab | Previous / Next Tab |
app.scrollActiveTabPageUp / app.scrollActiveTabPageDown | Scroll Active Tab Up / Down One Page |
app.splitTileHorizontal / app.splitTileVertical | Split Tile Horizontally / Vertically |
app.toggleLeftSidebar / app.toggleRightSidebar | Toggle Left / Right Sidebar |
app.openPreferences | Open Preferences |
app.openInExternalApp | Open in External App |
chat.sendMessage | Send Message |
chat.steerQueuedInput | Steer Queued Input |
terminal.toggleQuake | Toggle Quake Terminal |
terminal.openQuake / terminal.closeQuake | Open / Close Quake Terminal |
terminal.lineStart / terminal.lineEnd | Go to Line Start / End |
terminal.wordLeft / terminal.wordRight | Go to Previous / Next Word |
keybindings setting ever holds a value the client cannot read, the engine treats it as “no overrides” and silently ignores it, falling back to the defaults — so a malformed value never breaks the default shortcuts.Desktop (Tauri) accelerator differences
On the desktop app, a small number of shortcuts are mirrored onto the native menu bar as accelerators, but only on macOS. Windows and Linux render their own controls through the app’s custom title bar instead.
Two commands get their accelerators synced onto the macOS menu:
- Open Preferences (
⌘,) appears as Preferences… in the LeapMux Desktop application menu (the leftmost app menu on macOS). - Open Web Inspector appears as Open Web Inspector in the Help submenu.
If the Open Preferences binding is overridden, the macOS menu accelerator updates to match.
$mod+k $mod+s) still works as a regular shortcut, but it will not appear as an accelerator on the native menu item.The macOS menu also carries the usual system items in each submenu (About, Services, Hide, Quit, the standard Edit and Window commands, and so on). These come from macOS, not from the LeapMux shortcut registry, so their accelerators are the OS defaults you already know and are not affected by keybinding overrides.
Chat editor keys (not part of the global system)
The chat message editor has its own in-editor key handling that is separate from the global shortcut registry described above. The most important of these is how Enter behaves, governed by the Enter key mode preference (a per-browser setting; the default is Cmd/Ctrl+Enter sends):
- Cmd/Ctrl+Enter sends (default):
⌘Enter/Ctrl+Entersends the message; a plainEnterinserts a newline. - Enter sends: a plain
Entersends the message;Shift+Enterinserts a newline.
You can flip between the two from the composer’s [+] menu. Other editor keys handle Markdown structure inside the message box — for example, Tab/Shift+Tab adjust list and heading levels, Backspace lifts blockquotes and code blocks, and Cmd/Ctrl+E toggles inline code. These editor keys are not bindable through keybinding overrides.
For more on writing and sending messages to agents, see Coding Agents.
How matching works under the hood
You do not need any of this to use the shortcuts, but it explains a few edge cases:
- Keys are matched by physical position. Single-letter keys are matched by the key’s physical position on the keyboard (the browser’s
event.code), not by the character it produces. This is deliberate: on macOS WebKit, holding Option transforms the character (for example,Cmd+Alt+Nwould otherwise produce˜). Position-based matching keeps shortcuts working across keyboard layouts and modifier combinations. - First match wins. When a key matches more than one binding, the first binding in registration order whose
when-clause is true runs; LeapMux then prevents the browser default and runs the command.
See also
- Tabs & Layout — the tabs, tiles, and sidebars these shortcuts drive.
- Terminals — terminal behavior and the macOS cursor-motion shortcuts.
- File Browser — the directory tree refreshed and filtered by the Files shortcuts.
- Settings & Preferences — the Preferences dialog and where account vs. browser settings live.
- Running LeapMux — solo vs. distributed mode, which affects the “Open in External App” shortcut.