Terminals & panes
Every terminal in Conductor is a real PTY: one portable-pty
session per pane in Rust, rendered with xterm.js in the
webview. Your login shell runs with TERM=xterm-256color, rooted at the pane's working
directory.
Tabs
Each tab is an open session — either a terminal (with its own split layout) or an
Elyra agent panel. Open a new terminal tab with the + button, ⌘K → New
terminal tab, or by selecting a project.
- Drag to reorder tabs — grab a tab and drag it sideways; a blue insertion marker shows where it will land. (Reordering uses pointer events, so it works reliably inside the webview.)
- Jump to a tab with
⌘1–⌘9(its position in the bar, left to right). The shortcuts follow whatever order you've dragged the tabs into. - Close a tab with its ✕, or close the focused pane with
⌘W(closing the last pane closes the tab). - A tab is labelled by its project name, so you can always tell which project it
belongs to. When a foreground command is running (e.g.
⌘R/npm run dev), a pulsing marker and a small process chip (vite,php, …) appear next to the name.
Split panes
Split any pane to run multiple processes side by side:
| Action | Shortcut |
|---|---|
| Split right (vertical divider) | ⌘D |
| Split down (horizontal divider) | ⇧⌘D |
| Close active pane | ⌘W |
Splits nest freely, and you can drag the dividers to resize. Each pane also has hover controls in its top-right corner (split right / split down / close).
Move between panes with ⌘⌥ + the arrow keys, and zoom the active pane to fill the
tab with ⌘⌥Z (press again to restore) — tmux-style. Switching tabs, splitting, or
navigating unzooms automatically.
Global scrollback search
Press ⇧⌘F (or the command palette → Search all terminals) to search the buffer of
every open terminal at once — not just the focused one. Matches are grouped by pane with
a count and a sample line; pick one to jump straight to that tab/pane with the match
highlighted. Great for “which of my six running services logged that error?”
Why splitting never loses your session
Terminals live in a flat, absolutely-positioned layer keyed by terminal id. A pure
layout module (layout.js) computes each pane's geometry from a split tree. Because the
DOM node for a terminal is never remounted when the tree changes, the PTY session and
scrollback survive every split and divider drag. (Rendering the tree recursively would
have killed and respawned shells on every relayout.)
See Architecture & boundaries for the full data flow.
Notification rings
Background tabs pulse green when their terminal produces new output, and the indicator clears when you switch to the tab. All tabs stay alive in the background, so nothing is lost when you switch away from a long-running process.
A tab whose last command failed pulses red instead, and its border turns red — a failure outranks “there is new output”. Unlike the green ring it does not clear when you look at the tab, because noticing it is the whole point. It clears when you either:
- click the red dot, which hands the command, exit code, output tail and git branch to an Elyra agent for diagnosis (the same context as the Fix it toast, but available long after the toast has gone), or
- run something that succeeds in the same tab, which supersedes the old failure.
Requires shell integration for exit codes. Interrupts and
signals you caused yourself (Ctrl-C, SIGPIPE, SIGTERM, job control) never raise it.
Without the Elyra CLI installed the dot is still shown, and clicking it opens the tab.
Finished-command notifications
When a long-running command in a background tab returns to the shell — a build, test run, or deploy completing while you're in another app — Conductor fires a native notification (e.g. “✓ cargo finished · my-project · ran 54s”). It watches the foreground-process titles it already polls, so there's nothing to configure per command:
- Only fires for commands that ran at least ~8 seconds (no noise from quick commands).
- Never fires for the tab you're actively watching (focused window + active tab).
- Toggle it from the command palette — Notify when a background command finishes / Disable finished-command notifications. The choice persists across restarts.
First use asks for macOS notification permission; you can also manage it later under System Settings → Notifications → Elyra Conductor.
Shell integration (zsh)
On by default, shell integration makes new terminals capture the real command
line and exit code of each command you run, via OSC 133/633 sequences. Toggle it from
the command palette (⌘K → Enable/Disable shell integration); your choice is remembered.
It powers the command timeline, the persisted history, and "Fix it".
It's safe with your existing setup: Conductor points ZDOTDIR at a small shim that sources
your own .zshenv / .zprofile / .zshrc (so your prompt — powerlevel10k, instant
prompt, aliases — is untouched) and then adds precmd/preexec hooks.
With it on:
- The command timeline (🕘) shows the full command and a ✓ / ✗ exit-code badge, and every command is saved to a searchable history — see Command history & insights.
- The sidebar shows a per-project ✓ / ✗ test badge from the last test run (pest, phpunit, vitest, jest, pytest, cargo/go test, …).
Only zsh is supported for now; other shells keep the lighter, process-name-based timeline.
In-terminal search
Press ⌘F while a terminal is focused to open a find bar. Use ↵ / ⇧↵ to step
through matches and Esc to close. Powered by the xterm search addon.
Clickable links
URLs in terminal output are underlined on hover and open in your system browser when
clicked (e.g. a dev server printing ➜ Local: http://localhost:1420). Links open
via the OS, not inside the app window.
Font size
⌘+ and ⌘− grow and shrink the terminal font across all panes at once; ⌘0
resets it to the default (13px). The chosen size persists across restarts (it's part of
the saved layout). Emoji and wide CJK glyphs are measured at their correct width
(Unicode 11), so box-drawing in TUIs stays aligned.
Ask about this terminal (⌘↵)
Press ⌘↵ in any terminal pane to open an inline ask bar; Esc dismisses it. Type a
question and Elyra answers in place.
It works wherever the shell is — a local zsh, an SSH session, docker exec, a REPL —
for one reason: the context that matters is the scrollback, and Conductor holds that
locally regardless of where the shell actually runs.
What gets sent
- the pane's working directory, and the git branch if there is one
- the last 5 commands in that pane with their exit codes (needs shell integration)
- the last 120 lines of output, with credential-shaped text masked by the same redaction used for persisted scrollback
Redaction is not optional here. Terminal output routinely contains tokens, and unlike saved scrollback — which stays on your disk — this text leaves the machine for a model provider.
It answers; it does not act
Tools are disabled, so the bar cannot edit files or run commands. Where the answer contains a shell command, you get an Insert button that places it on the prompt line without pressing Enter, so you read it and run it yourself. An LLM suggestion is not consent.
Only fenced bash/sh/zsh blocks are offered — a command mentioned in prose is
deliberately ignored. When you want an agent that can actually change things, Open in
Elyra ▸ escalates to a full agent tab where there is a transcript to review.
No keys in Conductor
There is no provider or API-key setting for this, in Settings or anywhere else. Conductor
pipes your question to elyra --print, which already owns providers, models, and
credentials. Configure them with elyra config. See
Architecture.
Newline vs. submit (⇧↵)
Many TUIs use ⇧↵ to insert a newline while ↵ submits. Plain xterm.js can't send a
distinct sequence for modified Enter — it collapses ⇧↵, ⌥↵, and ⌃↵ to a bare
carriage return. Conductor fixes this by emitting the Kitty keyboard-protocol CSI u
sequence for modified Enter, so apps like the Elyra CLI receive ⇧↵ as a real newline
instead of submitting. No configuration is needed.
Broadcast input (synchronize panes)
Toggle ⌁ Sync in the top actions (or ⌘K → Broadcast input to all panes) to
mirror your keystrokes to every pane in the active tab — tmux synchronize-panes-style.
Useful for running the same command across several services in a monorepo. Toggle it off
to return to typing in just the focused pane.
Scrollback across restarts
Each pane's recent output is persisted and replayed as read-only history the next time Conductor launches, with a fresh shell started beneath it. A live PTY cannot be revived once the app closes, so this restores context, not a live process. Details and limits are in State & persistence.
Secrets in saved scrollback
Saved scrollback is stored in localStorage as plain text, and terminal output regularly
contains credentials — a cat .env, an exported token, a connection string printed by a
migration. Conductor masks the common shapes before writing: *_TOKEN / *_PASSWORD
assignments, Bearer tokens, GitHub / AWS / Stripe / Slack / OpenAI keys, JWTs, PEM
private-key blocks, and credentials inside URLs all become •••REDACTED•••.
That is a mitigation, not a guarantee — a secret in an unusual format can still slip
through. If you would rather nothing were written at all, ⌘K → Stop saving terminal
scrollback to disk turns it off and deletes whatever is already stored.
Running a command on open
Tabs opened for a task (or a file run) start a shell and then run the chosen command once. This is the same mechanism behind Run: … in the command palette.
Rendering & performance
Terminals are tuned to stay smooth even with several repaint-heavy TUIs (e.g. multiple Elyra agents) streaming at once:
- GPU rendering — xterm renders via WebGL, offloading the work from the main thread. If a GPU context is unavailable (or lost when many are live), the affected pane falls back to the DOM renderer automatically.
- Batched output — PTY output is coalesced into one write per animation frame, so a fast stream of small chunks doesn't multiply parse/render work.
- Binary streaming — PTY bytes reach the webview over a binary channel (an
ArrayBuffer), not a JSON event, avoiding a ~3.6× size hit and aJSON.parseper frame. - Hidden panes keep their size — an inactive (hidden) pane is never measured at 0×0, so switching or closing tabs doesn't shrink or garble the terminal you land on.