Skip to content

Keyboard and pointer

Every control Tenon adds, on one page. Everything not listed here belongs to the program running inside the pane — Tenon does not intercept it.

Keyboard

ActionShortcut
New tab⌘T
Split active pane left / right⌘D
Split active pane top / bottom⇧⌘D
Close active pane⌘W
Next pane⌘]
Next / previous tab⇧⌘] / ⇧⌘[
Command palette⌘K
Cancel a pointer transactionEsc

Plugins can register host-wide keybindings for the intents they own. Those are discoverable and rebindable, and they appear alongside the built-in ones rather than shadowing them. A keyboard control that only works inside one focused view is that view's own business and is not registered globally.

Pointer

ActionGesture
Move a panedrag its header, drop on empty grid
Swap two panesdrag one header, drop on the other pane
Resizedrag any pane edge or corner
Resize coupled neighboursdrag a shared edge
Reorder tabsdrag a tab chip along the strip
Resize the sidebardrag its trailing edge

Every edge and corner is a resize target even though nothing draws a handle there — aim at the boundary, not at a widget.

Escape is a real undo

Esc during a drag or resize restores the exact layout captured when the pointer went down.

This is stronger than it sounds. A pointer transaction is validated as a whole against the tab it started from: if the baseline no longer matches, the proposal is invalid, or the panes it claims to affect are not the panes it would actually change, the whole thing is refused. There is no partially-applied layout to recover from, so Esc is always safe to reach for.

What Tenon does not take

The pane is a real libghostty surface. Keyboard, mouse, clipboard, focus, scaling and resize integrate natively, and your program's own keybindings reach it unmodified — Tenon adds the shortcuts in the table above and does not filter anything else on its way to the PTY.

Terminal environment

Every terminal Tenon opens identifies itself to the process inside it:

VariableWhat it is
TENON_PANE_IDthis pane's stable UUID — the scope every tenon-cli verb defaults to
TENON_SOCKET_PATHthe running instance's control socket
TENON_TAB_ID, TENON_WORKSPACE_IDthe tab and workspace that owned the pane when the shell started

TENON_PANE_ID follows the pane wherever it is moved. The other two are a snapshot taken at shell start: move the pane to another tab and they go stale, so ask for the live answer instead of trusting them:

sh
tenon-cli intent send workspace.pane.owner.v1 \
  --input '{"paneID":"'"$TENON_PANE_ID"'"}'

An agent pane also carries hook and surface tokens used by Agent Lens. The complete list, including the development overrides, is in Environment variables.