Docs

Split Pane Shortcut: cmd-k + Arrow

cmd-k followed by an arrow key splits the active pane in that direction, copying the active item into the new pane. Works for both browser tabs and file editor tabs, including after theme switches.

How it works

There are two separate keyboard paths depending on where keyboard focus is. They are orthogonal and must not be collapsed into one.

Path 1 — Host renderer has focus

A keydown listener registered in capture phase on document during tranquil-browser activation intercepts the chord:

tranquil-browser.js activate()
  document.addEventListener('keydown', handler, true)   ← capture phase, fires first
    cmd-k → sets splitChordPending
    arrow → stopImmediatePropagation()                  ← blocks Pulsar's body keymap
             paneForItem(getActivePaneItem())[splitMethod]({ copyActiveItem: true })

stopImmediatePropagation() on the arrow key prevents Pulsar’s own pane:split-*-and-copy-active-item body binding from firing a duplicate split.

Path 2 — Webview has focus

When the user has interacted with browser content, keyboard events go to the webview process instead of the host renderer. bp-client.js (the webview preload) forwards every keydown via IPC:

bp-client.js (webview preload)
  window.addEventListener('keydown')
    ipcRenderer.send('webview-key-events', { w_event, ... })

cz-init.js (main process)
  ipcMain.on('webview-key-events')
    adds webContentsId, forwards to renderer via BrowserWindow.webContents.send()

utils.js addIpcInstanceEvents() (renderer)
  ipcRenderer.on('webview-key-events')
    ┌─ browser model is active ──→ keyHandler() on TranquilBrowserView
    │                                 chord detection → paneForItem(this.model)[splitMethod]()
    └─ non-browser item is active → _stuckCmdKPending chord
         (webview has stolen focus)   paneForItem(getActivePaneItem())[splitMethod]()

The “non-browser item active” branch covers the post-theme-switch case where clicking a file tab doesn’t return keyboard focus to the host renderer before the user tries to split.

Key invariants

  • Always use paneForItem(item), never getActivePane(). paneForItem looks up pane by item membership and is immune to focus-event side effects. getActivePane() reflects “last-activated” pane state which gets corrupted by any webview.blur() that triggers handleFocus → pane.activate() on the browser pane.

  • The body capture listener is the authoritative handler for host-renderer splits. Do not remove it or try to replace it with Pulsar’s keymap bindings — those bindings only fire when keyboard focus is in the host renderer and the listener can’t cross theme switches reliably.

  • The webview IPC path is additive, not a replacement. It covers the subset of cases where the webview process has keyboard focus.

Files

FileRole
tranquil-browser/lib/tranquil-browser.jsBody capture listener registered in activate()
tranquil-browser/lib/utils.jsWebview IPC handler (addIpcInstanceEvents)
tranquil-browser/lib/tranquil-browser-view.jskeyHandler() — browser-tab chord and split
tranquil-client/src/main-process/cz-init.jsIPC bridge: webview keydown → renderer
tranquil-browser/resources/bp-client.jsWebview preload: sends every keydown via IPC