# Terminal — Tabs, Splits & Panes
> The terminal layer: tabs within a worktree, splitting a tab into panes, the
> Ghostty engine underneath, tab titles & icons, shell integration, and fonts.
**Keywords:** terminal, tab, split, pane, surface, ghostty, font size, shell integration, OSC 133, scrollback, find, search, close tab, new tab, tab title, tab icon, CJK, font fallback, Japanese, Hiragino, ghostty config, dedicated config
**Related:** [concepts](../concepts.md) · [canvas](canvas.md) · [shelf](shelf.md) · [agent-detection](agent-detection.md) · [cli](cli.md) · [keyboard-shortcuts](../reference/keyboard-shortcuts.md)
## What it is
Inside each worktree you get terminal **tabs**. Each tab is a layout of one or
more **panes** (a.k.a. surfaces) — split horizontally/vertically — rendered by the
embedded **Ghostty** engine. One worktree → many tabs → each tab → one or more
panes.
```
Worktree
└─ Tab (has a title + icon)
└─ Pane / surface (a live Ghostty terminal; tabs can hold several, split)
```
A single Ghostty app instance hosts every pane as an independent surface, which is
why Prowl is fully native and CJK-correct.
## Tabs
| Operation | How |
|-----------|-----|
| New tab | Terminal menu → **New Terminal** (Ghostty `new_tab`, typically `⌘T`); the **+** on a Shelf spine; the [`prowl create tab`](cli.md) CLI |
| Select tab 1–9 | `⌘1`–`⌘9` |
| Previous / Next tab | `⌘⇧[` / `⌘⇧]` |
| Close focused tab | Terminal menu → **Close Terminal Tab** (Ghostty `close_tab`) |
| Close (right-click a tab) | Close Tab · Close Other Tabs · Close Tabs to the Right · Close All |
| Undo a close | Ghostty `undo` (typically `⌘Z`) within `undo-timeout` (5 s default) restores the last closed tab or pane in place; `redo` (`⌘⇧Z`) closes it again. See [undo close](../reference/keyboard-shortcuts.md#undo-close) |
| Rename tab | Right-click → **Rename Tab** (sets a custom title) |
| Change tab icon | Right-click → **Change Tab Icon** (pick an SF Symbol) |
| Reorder tabs | Drag tabs in the tab bar |
In **Shelf** view, tabs of the open book are also cycled with `⌘⌃↑` / `⌘⌃↓`.
## Splits (panes)
Splitting is handled by Ghostty actions (bind/keys in your Ghostty config):
| Operation | Ghostty action |
|-----------|----------------|
| New split (vertical / right) | `new_split:right` |
| New split (horizontal / down) | `new_split:down` |
| Focus adjacent pane | `goto_split:left/right/up/down`, or the app shortcuts below |
| Resize split | `resize_split:
:` |
| Equalize splits | `equalize_splits` |
| Zoom / maximize a pane | `toggle_split_zoom` (app default `⌘⌥⇧F`) |
| Close a pane | `close_surface` (Terminal menu → **Close Terminal**) |
App-level pane navigation (works inside the terminal too): `⌘[` / `⌘]` previous /
next pane, `⌘⌥↑/↓/←/→` for directional pane focus, `⌘⌥⇧F` to zoom / unzoom the
focused pane. Shelf spines also expose **split vertical / split horizontal**
buttons on the open book.
Zoom and close also have mouse affordances: hovering a pane's top drag handle
reveals a zoom button and a close button in the pane's top-right corner. The
close button closes that pane with the same confirmation as the tab bar's close
button, and its tooltip shows the `close_surface` shortcut (⌘W by default) so
the keyboard route is discoverable. A zoomed pane keeps both buttons visible so
it's always clear how to leave zoom.
## Tab titles — important caveat
A tab's displayed title is, in order of precedence:
1. a **custom title** you set via Rename Tab, else
2. the **live shell title** the running program emits (OSC 2), else
3. an auto-generated default like `project 1`, `project 2`.
Rapid live-title animation is coalesced per tab to at most one visible update
per second so one spinner frame does not rebuild the entire tab bar. The newest
withheld title is applied at the end of that interval even when no agent is
running, so a live title may visibly lag by up to one second but its final value
is not left behind.
The Run Script tab is labeled **RUN SCRIPT** and is **title-locked** for its
lifetime. Prowl also "learns" your shell's idle prompt so it doesn't mistake it
for a meaningful title.
> **Titles are free-form and can lie or lag.** Any program can set any title.
> When automating, never target a pane by its title — use the JSON `pane.id` or,
> for a same-session handoff, the text `pN` handle from
> [`prowl list`](cli.md). The bundled [`prowl-cli` skill](cli.md) repeats this
> for good reason.
## Tab icons
Tabs get an auto-detected icon based on what's running (e.g. an agent's icon),
which you can override (Change Tab Icon) or which a script can set. Icon
precedence is `auto < script < user`.
An Agent Profile launch applies that runtime's brand icon itself instead of
waiting for detection — to the new tab, or to the containing tab when the
profile opens in a split. Auto-detection reads the first token of the command,
and a profile that sets launch-scoped environment variables runs as
`env VAR=… claude`, whose first token is `env`. The slot stays claimable, so a
later recognised command in the same tab still takes it over.
While a pane has a detected agent, its titles do not change the icon: an agent's
title is its own text, such as the conversation topic, not a command line. The
tab takes the agent's icon when detection first sees the agent, so an agent
started through an alias gets the right icon too. Only the focused pane of a tab
can change the tab's icon. Without a detected agent, a program's title still
counts as a command, so a title that starts with a known command name (for
example `Codex thread`) sets that icon.
In a tab with splits, focusing a pane with a detected agent gives the tab that
agent's icon, unless a user or script icon is set. Focusing a pane without an
agent does not change the icon: the next recognised command in that pane does.
A tab with a pane whose foreground `ssh` reaches a remote host also shows a network glyph
before its title, and the tooltip names the host. The pane itself shows `user@host` in its
bottom-trailing corner for five seconds when the label appears or changes. Then only the
network glyph stays, so the label does not cover the terminal's bottom line (for example a
tmux status line); put the pointer on the glyph to see the host again. After that `ssh`
exits, the corner reads "Disconnected from user@host" for five seconds. A yellow warning
glyph in that label means Prowl cannot read the pane's agents; its tooltip says why. See [agent detection](agent-detection.md#remote-agents-over-ssh).
## Shell integration & status (OSC sequences)
Ghostty's shell integration drives several Prowl features via terminal escape
sequences:
- **OSC 133** (prompt/command marks) — lets Prowl know when a command **starts and
finishes** and its **exit code**. This powers command-finished notifications,
auto-close-on-success, and the `prowl send --capture` output capture.
- **OSC 2** (title) — feeds tab titles and agent/icon detection.
- **Progress reports (OSC 9;4)** — long commands report busy/idle, feeding the
tab's activity indicator and task status. Task status *also* folds in detected
agent activity (Working/Blocked, incl. background workflows) — see
[agent-detection](agent-detection.md#worktree-running-indicator).
- **Program status (OSC 7501)** — Prowl answers the support query (protocol
revision 0.4: `?` plus the states and kinds it accepts), so programs
that speak the protocol (Claude Code 2.1.295+, Pi 1.1.0+, Oh My Pi 18.8.7+,
Codex 0.163.0+, and recent Amp, Antigravity, Devin, Droid, and Grok Build)
report their state. Codex sends idle/working/blocked; the other verified
producers also send done. Reports without a support query are accepted. Prowl keeps one record tree per pane with the
protocol's lifetime rules (a report replaces its record, `clear` removes a
subtree, `app` is inherited, at most 256 records) and feeds the root record to
agent detection — see
[agent-detection](agent-detection.md#program-status-osc-7501) for which
producers it trusts. OSC 133 also tells detection that the foreground command
ended, which releases an agent's entry within one probe (except after Oh My Pi
has drawn a user message: its own OSC 133 marks use up the shell's command
timer, see [agent-detection](agent-detection.md#program-status-osc-7501)).
- **Bell / desktop notification** — increments unread indicators.
`--capture` in the CLI **requires OSC 133** on the target pane; without it you get
`CAPTURE_UNSUPPORTED` (read the screen with `read --wait-stable` instead).
Every pane's environment reports **`TERM_PROGRAM=prowl`** (with
`TERM_PROGRAM_VERSION` set to the app version), so scripts and agents can detect
they're running inside Prowl rather than standalone Ghostty. `TERM` itself stays
`xterm-ghostty` — terminfo-based feature detection is unaffected.
## Command-finished behavior
When a command finishes (via OSC 133), Prowl can:
- fire a **notification** if it ran longer than the threshold (default 10s) — see
[notifications](notifications.md);
- **auto-close** the tab/pane if it was launched with "close on success" and the
exit code is 0;
- skip the notification for user-initiated exits (Ctrl-C → 130, SIGTERM → 143) or
if you typed in that pane within the last ~3 seconds.
## Font size
- **Reset / Increase / Decrease** via the Terminal menu (Ghostty
`reset_font_size` / `increase_font_size:1` / `decrease_font_size:1`; usually
`⌘0` / `⌘+` / `⌘-`).
- The chosen size is remembered (`terminalFontSize`) and applied across worktrees;
new tabs and splits inherit the focused pane's size.
- Going back to Ghostty's `font-size` (for example with **Reset**) clears the
remembered size, so later changes to `font-size` apply again. A remembered size
that equals the current `font-size` is cleared at launch.
## In-terminal search
Terminal menu: **Find…** (`start_search`), **Find Next/Previous**
(`search:next`/`search:previous`), **Hide Find Bar** (`end_search`), **Use
Selection for Find** (`search_selection`). These are Ghostty-managed.
## Scrollback, CJK, copy/paste
Scrollback, wide/CJK character rendering, and copy/paste are handled by Ghostty
with its defaults. Customize terminal behavior in the Ghostty config that Prowl
uses (see [Ghostty config](#ghostty-config)).
**Clipboard confirmations.** Prowl asks before a clipboard action that Ghostty
holds for confirmation. The Ghostty config decides which actions these are:
| Action | Config (default) | When Prowl asks |
| --- | --- | --- |
| Paste (⌘V, **Edit > Paste**) | `clipboard-paste-protection` (`true`), `clipboard-paste-bracketed-safe` (`true`) | The text contains a line break or carriage return and the program has not turned on bracketed paste, or the text contains the bracketed paste end sequence `ESC [ 2 0 1 ~` |
| A program reads the clipboard (OSC 52, Kitty clipboard protocol) | `clipboard-read` (`ask`) | Every read, while the value is `ask` |
| A program writes the clipboard (OSC 52, Kitty clipboard protocol) | `clipboard-write` (`allow`) | Every write, while the value is `ask` |
The sheet shows the text that would be pasted, read, or written, with control
characters shown as `\u{…}`, or the image for an image read. **Cancel** or
**Deny** is the default button. For a Kitty clipboard request that carries a
password, **Remember this choice for this session** with **Allow** lets later
requests with that password in the same pane through without a question. A request from a pane that is not focused
waits and rings that pane's bell; the sheet opens when you focus the pane. A
pane has at most one waiting request: a newer request denies the older one,
and closing the pane denies it. Set `clipboard-paste-protection = false` or
`clipboard-read = allow` to turn the questions off. Text that Prowl or the
`prowl` CLI sends to a pane does not go through the clipboard and is never
held. Dropped text is not checked yet.
**CJK font fallback.** When the active Ghostty config sets neither `font-family`
nor `font-codepoint-map` (including its `config-file` includes, the active theme
file, and `--font-family=…` launch arguments), Prowl maps kana, CJK punctuation,
CJK ideographs (including the supplementary planes), and fullwidth forms to one
system CJK font, and Hangul to Apple SD Gothic Neo. Without this, Ghostty picks a font for kana and
punctuation by scoring every installed font, which often selects BIZ UDGothic,
BIZ UDMincho, or Osaka-Mono, so CJK text changes face and size within a line.
The font follows the first CJK language in the system language list (a
`-AppleLanguages` launch argument wins; Prowl's own app language does not count):
| First CJK language | Font |
|---|---|
| Japanese | Hiragino Sans |
| Korean | Apple SD Gothic Neo |
| Traditional Chinese (Taiwan) | PingFang TC |
| Traditional Chinese (Hong Kong, Macau) | PingFang HK |
| Simplified Chinese, or no CJK language | PingFang SC |
Set `font-family` or `font-codepoint-map` to take over; Prowl then adds nothing.
**Links.** ⌘-click opens a link from terminal output: an OSC 8 hyperlink or text
that matches a Ghostty `link` entry (by default, URLs). Any program can print
such a link, so Prowl checks the target first:
| Target | Result |
|---|---|
| `http`/`https` with a host, `mailto` with an address | Opens directly |
| Local file or folder (`file://` or a plain path such as `~/notes.txt`) that exists and cannot run code | Opens directly |
| Any other scheme (for example `vscode://`) | Opens after a confirmation that names the app |
| Application bundles, scripts, executables, installer and shortcut files, missing files, remote `file` hosts, targets with invisible or line-breaking characters, values without a scheme | Blocked with an alert that offers **Copy Link** |
Symlinks and Finder aliases are judged by the file they point to, and that file
is what opens. A relative path is resolved against Prowl's own working
directory, not the pane's, so it is usually blocked as missing.
## Ghostty config
By default Prowl reads the same config files as the standalone Ghostty app
(`config` or `config.ghostty` in `~/.config/ghostty/` and in
`~/Library/Application Support/com.mitchellh.ghostty/`, plus their `config-file`
includes), so a change applies to both apps.
**Settings → General → Terminal Config → Choose File…** selects a dedicated file
for Prowl only (`ghosttyConfigPath`). That file **replaces** Ghostty's config
files; it is not layered on top of them, so copy any setting you want to keep.
Relative `config-file` includes resolve from the file's folder. If the file does
not exist, terminals use Ghostty's built-in defaults and Settings shows a
warning; **Open Config** creates it. **Use Ghostty's Config** returns to the
shared files. **Open Config** and **Reload** act on whichever source is active,
and a change of source applies to running terminals immediately.
Prowl-only keys such as `prowl-split-divider-width` are read from the active
source's file.
**Environment variables.** The Ghostty `env` key (`env = NAME=value`, one line
per variable) sets variables in every terminal that Prowl starts. It applies
after the variables that Ghostty sets, so it can also override them.
## Ghostty CLI and SSH integration
The app bundle contains `Contents/MacOS/ghostty`, a link to the app executable.
Under that name, the executable runs Ghostty CLI actions with the Ghostty
version that Prowl embeds (`ghostty +version`, `+list-themes`, `+show-config`,
`+ssh`, `+ssh-cache`, …) and exits; it never opens a window, and without an
action it prints a hint and exits with status 1. Prowl panes set
`GHOSTTY_BIN_DIR` to that folder, and the default `path` shell integration
feature appends it to `PATH`, so `ghostty` resolves to it unless another
`ghostty` comes earlier in `PATH`. To use it outside Prowl, link it from a
folder in `PATH` (for example
`ln -s /Applications/Prowl.app/Contents/MacOS/ghostty ~/.local/bin/ghostty`);
it still finds the resources in the app bundle.
This makes Ghostty's SSH features work in Prowl panes:
`shell-integration-features = ssh-env,ssh-terminfo` wraps `ssh` in
`ghostty +ssh`, which installs the `xterm-ghostty` terminfo on a remote host on
the first connection and forwards `COLORTERM`, `TERM_PROGRAM`, and
`TERM_PROGRAM_VERSION`. The install cache (`ghostty +ssh-cache`) is
`$XDG_STATE_HOME/ghostty/ssh_cache` (default `~/.local/state/ghostty/`), the
same file that Ghostty.app uses. Remote folders that Prowl opens over ssh run
`/usr/bin/ssh` directly and do not use this wrapper.
## Color scheme
The terminal theme automatically follows the macOS light/dark appearance. There's
no in-app Ghostty theme picker; change Ghostty colors via its config file.
## Layout persistence
`restoreTerminalLayout` is on by default (Settings → General → Terminal
Layout). Prowl saves the tab/split layout (which worktrees, tabs, split trees,
titles, icons, each pane's folder) and restores it on next launch. Restored panes
start a new shell: running commands and agents do not start again, and a folder
outside the worktree falls back to the worktree root. It saves when you quit and
each time Prowl stops being the active app, so a crash loses only the changes made
since you last switched away. Notification bodies are not persisted.
If a saved worktree is gone at launch (it was removed outside Prowl, or its
repository failed to load), Prowl skips it and restores the rest. The next save
no longer holds the skipped worktree, so its layout does not return if the folder
comes back later. If the saved layout names no available worktree, Prowl selects
the last focused worktree, else the first available worktree or folder. A saved file that
cannot be read is removed without a message. A saved layout that cannot be applied
is reset, and Prowl shows a warning.
When the default view is Canvas, launch focuses the selected tab of the saved
worktree, so keyboard input goes to the highlighted card.
## Gotchas for agents
- **Tab selection ≠ pane focus.** A tab can have several panes; selecting a tab
doesn't pin which split has keyboard focus. The CLI's `pane.focused` is the
truth.
- Closing the **last** tab leaves the worktree with no visible terminal (Shelf
removes the book; Canvas drops the card). `⌘Z` within `undo-timeout` brings
it back.
- Closing a pane or tab asks for confirmation after editing within the last 10 seconds or
during input-method composition. See [recent input close protection](../reference/keyboard-shortcuts.md#recent-input-close-protection).
- A closed pane or tab stays restorable for Ghostty's `undo-timeout` (5 s by
default). Observers see the close immediately: `prowl agents wait` reports the
pane as gone, and a restored pane gets a **new** short handle. See
[undo close](../reference/keyboard-shortcuts.md#undo-close).
- `--capture` and stable reads depend on the pane's shell integration; agents
running full-screen TUIs may need `read --wait-stable` rather than `--capture`.