Configuration
Documentation index · Project overview
Every setting Corta has, in the one file that holds them:
~/.config/corta/config.
That file is the single source of truth for settings — what this
document covers. The Settings page (⌘,) edits it and reads the result
back; there is no second settings store. An edit made in $EDITOR while
Corta is running is picked up within a moment — the file and its directory
are both watched, because most editors write a temporary file and rename
it over the target rather than writing in place. App-owned history, window
arrangements and remote-edit copies are separate from settings (§8).
The file is created with the current values the first time the Settings
page is opened, so opening it once and then reading the file is the
fastest way to see the current defaults. For the separate development app,
the file is ~/Library/Application Support/Corta Dev/config; an explicit
CORTA_STAGE_DIR uses <stage>/config. See Testing.
1. Format
key = value # comment to end of line
- One setting per line. Whitespace around the key, the
=and the value is ignored. - A line whose first non-blank character is
#is a comment. - A
#that opens a value, or follows a comma in a list, is a colour, not a comment (background = #101018,ansi = #000, #c23621). The key is split off before comments are stripped, which is what makes both readings possible. - Booleans accept
true/false,yes/no,on/off,1/0. - An unknown key is preserved, not dropped: a file written by a newer Corta survives a round trip through an older one.
- An unparseable line is skipped, never fatal. A typo in one setting must not cost you every other setting, and the terminal has to start.
- A file that exists but is not UTF-8 text, or cannot be read, is never written: Corta keeps its current settings and Settings says why changes are not being saved, until the file reads again.
- Numeric values outside the documented range are clamped and written back in their clamped form. Invalid numbers, unsupported enum values and malformed shortcuts keep the default for that key when loading the file; their original lines are preserved on a settings round trip.
Three families of key are structured rather than scalar, and all use a
dotted prefix so the flat format needs no nesting: theme.<name>.…
(§4), preset.<name>.… (§4a) and bind.<command> (§5).
2. Settings
Appearance
| Key | Values | Default | Notes |
|---|---|---|---|
theme | a theme name, or light:<name>,dark:<name> | corta | One name serves both appearances; the pair gives light and dark mode a theme each, and a side left out is corta; a name with : or , in it cannot be chosen. Built in: corta, solarized and mono, all offered in the UI (§4). A theme defined in this file wins over a built-in of the same name. |
appearance | auto, light, dark | auto | Which of the theme's two variants is live. auto follows macOS and switches while running. |
status-bar | boolean | false | Show a bottom bar of local system metrics. Click it for local host details. Sampling pauses when every enabled bar is hidden. |
status-items | comma-separated cpu, load, memory, network, disk, thermal | all six | Choose metrics to collect and display. Empty shows the local-host details button without periodic sampling. |
status-network-interface | auto or an interface name | auto | Auto selects one primary interface (physical fallback); names such as en0 or utun2 select exactly that interface. An unavailable explicit interface shows no rate. VPN and physical traffic are never summed. |
cursor-shape | block, bar, underline | block | Default cursor shape, applied live. Terminal programs can temporarily override it with DECSCUSR; parameter 0 or a terminal reset restores this default. |
cursor-blink | boolean | false | Blink the default cursor every half second while its pane has keyboard focus and is visible at the live screen. Terminal programs can temporarily override blinking with DECSCUSR. |
font-family | system, or an installed family name | system | system is the macOS system monospaced font. Any other value is a family name (Menlo, JetBrains Mono) and is kept as written; it is used only if it is installed and its regular, bold and italic faces share one advance across printable ASCII (D12), otherwise System Monospaced draws instead and Settings ▸ Appearance shows why. Settings lists only the families that pass. Characters the family lacks come from font-family-cjk and the faces font-fallback lists. |
font-family-cjk | auto, or an installed family name | auto | The family Chinese, Japanese and Korean characters the font lacks are drawn in, first in the fallback. auto follows the system language (see font-fallback). Any other value is kept as written and used if it is installed and has Han, kana or Hangul in outline; it need not be monospaced, since it draws only CJK, centred and fitted to its cells, and the font keeps the cell size and baseline. What it lacks falls through to the auto faces. Otherwise auto applies and Settings ▸ Appearance says so. Unused while font-fallback is false. |
font-size | 10–24, whole points | 13 | Points; a fractional value rounds to the nearest whole point, applied immediately to panes without a temporary zoom and used by new windows and panes. Font changes never move or resize the window; they refit the terminal rows and columns to it, and what is left of a cell shows as background under the last row and past the last column. Very small windows remain subject to minimum-size constraints. ⌘+ / ⌘− / pinch (B09) are a temporary, per-window zoom that never writes here — zooming one window does not change this key, another open window, or what the next new window opens at; ⌘0 ends the zoom and returns to whatever this key currently says. New panes in the same window inherit its temporary zoom. A zoom does not survive quitting and relaunching Corta. |
font-weight | light, regular, medium | regular | The primary face's weight. A family without that face, or whose face fails the same width check as the family, draws its Regular (Menlo and PT Mono have only Regular and Bold; SF Mono and Fira Code have all three). Bold text is always the family's own bold. |
line-spacing | 1.0–1.4, in steps of 0.02 | 1.18 | The line's height as a multiple of the font size. A font that declares a taller line keeps it, so 1.0 never clips; CJK families that declare exactly one em get room between rows. Every cell in every language shares it. |
font-fallback | boolean | true | Characters the font lacks come from other fonts: PingFang SC or TC, Hiragino Sans and Apple SD Gothic Neo by the user's language (kana always Hiragino Sans, Hangul always Apple SD Gothic Neo), then Apple Color Emoji, then whatever macOS finds. false draws them as the missing-glyph box. |
emoji-rendering | color, monochrome | color | monochrome draws an emoji as a text glyph where the primary font, Apple Symbols or Menlo has one; an emoji none of them has stays in colour. Width follows the terminal's width rules either way. |
Window
| Key | Values | Default | Notes |
|---|---|---|---|
columns | 20–500 | 120 | The grid a new window opens with, in cells. |
rows | 5–300 | 30 | As above. The window's size in pixels is this grid times the font's cell metrics plus the pane insets — so two terminals showing the same columns × rows are still different sizes on screen if their fonts differ. |
restore-windows | boolean | true | Reopen window frames, tabs, splits, divider proportions and working directories on the next launch. Starts fresh child processes; terminal text and running jobs are not restored. |
confirm-close | boolean | true | Ask before closing a pane, window or the app while a shell still has a foreground job. |
Terminal
| Key | Values | Default | Notes |
|---|---|---|---|
scrollback-lines | 0–1000000 | 10000 | Lines of history per session. Applies to sessions opened afterwards: a running child's history cannot be re-limited without discarding lines. |
ambiguous-width | narrow, wide | narrow | East Asian Ambiguous characters (Unicode UAX #11, version 17.0) — ±, §, é, Greek, Cyrillic, box drawing among them — take one cell or two; wide box drawing spans both cells. Applies to output written after the change; what is already on screen keeps its width. Programs that measure text themselves (shells, editors) count by their own rules, so wide can misalign them. |
command-history-limit | 0–10000 | 512 | How many completed/running commands a session's structured command history (jumping, copy/export by identity, Command History search) keeps at once — separate from scrollback-lines, which bounds visible text, not command records. Applies to sessions opened afterwards, same reason as scrollback-lines. |
bell | visual, audible, muted | visual | visual flashes the pane; audible is NSSound.beep(). |
input-source-indicator | auto, always, off | auto | A persistent input-source badge in the window toolbar by default. auto enables it only when the user has enabled a non-Latin keyboard layout or an IME; always shows it for any input source. Every mode hides it in unfocused panes. In the toolbar it stays while a program runs, since it covers no output. With prompt placement, auto shows it at a ready shell prompt (without OSC 133 shell integration it falls back to the focused pane), always also during command output, and both hide it in scrollback and alternate-screen programs. With prompt placement, long input moves it downward without changing the grid; if no safe space remains it hides until the next prompt. Toolbar placement stays fixed. Applies immediately. Settings ▸ Keyboard & Mouse. |
input-source-indicator-position | toolbar, prompt | toolbar | Fixed window-toolbar status separated from network/file actions, or the original right-edge prompt overlay with long-command avoidance. Uses the focused pane’s state. Off and prompt placement remove the toolbar slot and its spacer. Applies immediately. Settings ▸ Keyboard & Mouse. |
input-source-direct-color | hex color, or empty | (empty) | Background for confirmed direct input (A). Empty uses quiet text over a faint fill: the theme's text colour when the badge sits beside the prompt, the system's in the toolbar. Accepts #rgb / #rrggbb; text automatically uses black or white for contrast. |
input-source-ime-color | hex color, or empty | (empty) | Background for a non-Latin keyboard layout or a reported built-in IME mode (中, あ, 한). Empty uses a soft indigo tint beside the prompt and gray in the toolbar. Third-party IMEs that do not expose their internal ASCII mode keep a neutral badge, independent of this color. |
option-as-meta | boolean | false | Whether ⌥ acts as Meta — an ESC prefix on the base character, the way a PC keyboard's Alt does — instead of composing the layout's alternate character. Off by default because on macOS ⌥ is text input: it types é, ø, –, and starts dead-key sequences, and an international layout needs that. Turn it on when a program wants M-x and M-b. Special keys are unaffected either way: ⌥ already reaches the child there as the xterm modifier parameter, and an IME still sees every event it would otherwise see. |
open-file-command | string | (empty) | The command run when a path:line reference in program output is ⌘-clicked. {file}, {line} and {column} are substituted, one argument at a time. The executable must be an absolute path and is run directly — never through a shell — so a path containing ; or $(…) stays a path. For both local and remote references, a nonempty editor command is required. Empty disables opening: system default handlers can execute some file types. The hover tooltip names the required setting. The same template is used for the managed local copy the remote-editing flow downloads — {file} is the copy, the line and column are the reference's own. |
search-regex | boolean | false | Whether the search field is read as a regular expression (ICU syntax, as NSRegularExpression accepts it). The * button in the search bar writes this key. Three things are reported rather than shown as "no results": a pattern that does not compile, a pattern whose shape makes a backtracking engine take exponential time ((a+)+, (a*)*, (a|a)+ — every one has a linear equivalent, and it is refused before it runs because ICU's time limit is not reachable from Swift), and a sweep that stopped on its 500 ms budget or on a line longer than 64,000 units, which the match count marks with a +. |
search-case-sensitive | boolean | false | Whether scrollback search distinguishes case. Off by default: a person searching a log for error wants Error and ERROR too. The Match Case button in the search bar writes this key, so the choice survives closing the bar and restarting. |
paste-warning | dangerous, always, off | dangerous | Warns for shell pipes (and bash <(curl …) / sh -c "$(curl …)" feeds), sudo, recursive forced deletion, backslash continuations, hidden Unicode characters, and line breaks without bracketed paste. always also warns for any multiline paste with bracketed paste enabled; off disables dialogs. Applies immediately. “Don’t warn again for this pane” lasts until the pane closes. |
copy-on-select | boolean | true | A finished selection goes straight to the clipboard, confirmed by a label in the corner of the pane. Set false for ⌘C only. |
mouse-override-modifier | option, shift, control | option | Hold this modifier when beginning a drag to select terminal text while a TUI owns the mouse. Applies immediately. The pane shows a hint on first use; copy-on-select controls automatic copying. |
link-activation | command, click | command | command opens a link on ⌘-click. click opens it on a plain click and underlines the link under the pointer; dragging across a URL still selects it. |
allow-clipboard-write | boolean | false | Whether OSC 52 may put text on the system clipboard — the only route from inside tmux or an ssh session. Off by default because any output could use it. The read direction does not exist under any setting (SECURITY.md §6). |
secure-keyboard-entry | boolean | false | Secure Keyboard Entry: while a Corta terminal window is key and Corta is the active application, the system stops delivering keystrokes to any other process — event taps, keyloggers, macro tools and accessibility clients alike. Shell ▸ Secure Keyboard Entry toggles this key and shows a checkmark; a lock in the titlebar shows when it is actually engaged, which is not while another app is frontmost or Settings is key. Off by default because it is system-wide and also silences the tools a person may rely on (SECURITY.md §4.5). |
Following a file reference
⌘-click on src/main.rs:42:17 in program output opens that file, at that
line where open-file-command can express one. A URL wins where both could
match.
Start with the editor's installed command-line executable. Run command -v code (or your editor's CLI name) in a shell and use its absolute path:
open-file-command = /usr/local/bin/code --goto {file}:{line}:{column}
Replace /usr/local/bin/code with the actual executable on your Mac. The
template is split on whitespace before placeholders are substituted; quoted
paths, ~, $HOME, pipes and other shell syntax are not interpreted. For an
executable in a path containing spaces, use a command-line launcher or symlink
whose path contains no whitespace. Referenced file paths containing spaces
remain one argument after substitution. Empty disables both local and remote
file opening.
Local and remote references share this editor template, but resolve in different places:
- A bare path is not a reference. Only
path:line— the shape a tool emits — is detected. Ordinary output is full of things that look like paths (and/or,n/a,TODO/FIXME), and underlining a third of every line teaches you to ignore underlines. - Local references: resolve against the pane's confirmed local working directory and must name an existing regular file. A remote OSC 7 report never becomes a local working directory.
- Remote references: use the pane's known remote host and directory. After host confirmation and SFTP authentication, Corta downloads a managed local copy and opens it with the configured editor. It never treats the remote path as a path on your Mac. The copy is limited to 64 MiB; uploading edits requires an explicit decision and a remote-content conflict check. See Remote work.
This does not widen the URL scheme allowlist (SECURITY.md §2.4). A
file:// string in output is still plain text and still cannot be detected as
a link. Output-derived files are opened only through your configured editor,
never through a system default handler. Paths are substituted as arguments
and the command runs directly, without a shell.
Shell integration
Two paths provide the same bundled hooks. Standard local zsh sessions load
them automatically when directory-completion = true or history-suggestions = true
(both default to true), without
editing startup files. Custom zsh arguments, bash, fish and remote shells
need explicit installation or their own compatible OSC 133 hooks. Disabling
suggestions in a live shell does not unload hooks already sourced;
new sessions with both settings off do not use the automatic zsh bootstrap.
Directory and history suggestions come only from that automatic bootstrap: installed
hooks give a shell the OSC 133 marks, not suggestions.
Startup-file installation is not a config-file key — it edits the login
shell's startup files, which only
Settings ▸ Terminal ▸ Shell Integration touches, and only inside one
marked block each. The shell is the one $SHELL names: ~/.zshrc for
zsh, ~/.config/fish/config.fish for fish, and zsh's for anything else.
bash takes two: Corta starts it as a login shell, which reads the first
of ~/.bash_profile, ~/.bash_login and ~/.profile that exists (a new
~/.bash_profile when there is none) and never ~/.bashrc, while a bash
started inside the session reads only ~/.bashrc — so the block goes into
both, and guards itself against running twice when one sources the other.
It only acts in an interactive bash, so bash -lc output and other shells
reading ~/.profile are untouched.
# >>> Corta shell integration >>>
…
# <<< Corta shell integration <<<
Install appends the block (creating the file first if it does not
exist); Remove deletes exactly that block and nothing else a user wrote
around or inside it — for bash, from every login file it may have gone
into — and deletes a file only if Install created it (its first line
says so) and nothing else is left in it. Installing is reversible for the same reason: nothing
outside those two lines is Corta's to change, so removing them undoes the
whole thing. Installing twice changes nothing the second time, and the
script itself guards its own hooks with CORTA_SHELL_INTEGRATION_ACTIVE in
case something else sources it again.
The row reports one of four states, read fresh from those files every time Settings opens — the file, not a cached flag, is the ground truth, the same rule the config file itself follows (§1):
| State | Meaning |
|---|---|
| Not installed | No Corta block. Offers Install. |
| Installed | The block is present and holds this version's hooks. Offers Remove. |
| Installed hooks differ | The block is present, but what sits between its marker lines is not this version's hooks — an earlier version's, or edited by hand — or, for bash, it is in one of its two files and not the other. Corta never rewrites the block on its own, so a fix to the hooks waits here. Offers Update, which replaces everything between the two marker lines, edits included, and leaves the block where it is; and Remove. A block whose end marker is missing reads as Installed, since there is nothing whole to update. |
| Possible conflict | No Corta block, but the file already sources another terminal's own integration (iTerm2, Starship, VS Code or WezTerm's are recognised by name). Offers Install Anyway — installing alongside another integration is not refused, only flagged, since only the user knows whether that is what they want. |
The installed script emits OSC 133 ; A/B/C/D and OSC 7 for the working
directory from the shell's own hooks — zsh's preexec/precmd, bash's
DEBUG trap and PROMPT_COMMAND, and for fish a wrapped fish_prompt plus
fish_preexec (fish 4 sends most marks itself, and the script then adds
only the one it lacks) — the same
two sequences Performer+ShellIntegration.swift and this document's
command-jump entries already describe. Startup-file blocks are installed
only on request. Automatically bootstrapped local zsh sessions have the
same command boundaries without such a block. A session with neither kind
of integration uses TaskNotifier's typing-and-idle heuristic.
Notifications
| Key | Values | Default | Notes |
|---|---|---|---|
notify-from-programs | boolean | true | Allow bounded program notifications and agent attention notices. Notification Center may retain their message text. |
agent-status | boolean | true | Show state on tabs, the Dock and the Agents list. Dedicated task-state integration currently covers Claude Code only; process-only detections show State unknown. |
agent-processes | comma-separated names | (empty) | Additional foreground executable names, up to 32 names of 80 bytes each; macOS reports at most the first 32 bytes of a process name, so a longer name never matches. Built-ins: claude, claude-code, codex, gemini, aider, opencode. Names enable process detection only, not task-state integration or hook installation. No polling timer. |
notify-on-long-task | boolean | false | Post a notification when a long command finishes. |
notification-threshold | ≥ 1 | 30 | Seconds a command must run to be worth one. |
Program notifications use notify-from-programs and the visibility/occasion
rules described in the user guide.
Claude's preferredNotifChannel and terminalProgressBarEnabled are Claude
settings, not Corta config keys. auto does not choose a message channel for
Corta. Select ghostty, iterm2 or kitty in Claude for message text;
notifications_disabled there does not disable Corta's fixed 7501 attention.
Corta keeps its own terminal identity and does not spoof another terminal to
enable Claude's progress channel. These notices are independent of the long-task threshold below. Process-name detection
does not provide dedicated task-state integration for another CLI.
A long-task notification (notify-on-long-task) is posted only when
all of these hold: the setting is on, the command ran longer than the threshold, the window is not the
key window, and macOS has granted notification permission. With shell
integration (OSC 133) the boundaries are exact; without it, Return starts
the timer and 1.5 s of output silence ends it — which is why the feature
is off by default. The notification carries the tab's name you gave it,
or else the pane's directory, and the exit status — never the command text
or a title a program set (SECURITY.md §5).
History
| Key | Values | Default | Notes |
|---|---|---|---|
history-suggestions | boolean | true | Faint suffix from the most recent matching command in local zsh’s in-memory history. Right or End accepts at the end of a nonempty, single-line input; Esc dismisses. Tab, Shift+Tab and Up/Down keep native bindings. cd retains directory-suggestion precedence while directory-completion is on. Disabled automatically when zsh-autosuggestions is loaded. No startup-file edits or history-file reads. Restart the shell after enabling. See History suggestions. |
directory-completion | boolean | true | Faint folder alternatives below cd, with an inline suffix as you narrow the matches in a standard local zsh session. Tab fills the selected path; Left/Right select the previous/next candidate; Esc dismisses. Up/Down retain shell history navigation and Shift+Tab goes to the shell. Set to false to disable suggestions and restore native key handling. New sessions load the bundled hooks without editing the user's startup files. Restart the shell after enabling. Bash and fish retain native completion. See Directory suggestions. |
command-status-marks | boolean | true | Show command outcomes in the left margin: green for exit 0, red for other errors, and a split gray line for interruption (exit 130). Hover for the textual outcome. |
directory-history | boolean | true | Whether Corta records the local directories a completed command actually ran in (OSC 7). Nothing in the app lists the record yet; Settings ▸ Privacy & Security ▸ History shows its size and clears it. |
Not the config file's own state: directory-history only gates whether
DirectoryHistoryStore reads and writes its file (Application Support, not
~/.config/corta/config — the same split SessionRestore draws between
settings and app-managed state). Turning the key off stops it being read
or written; Clear in Settings ▸ Privacy & Security ▸ History empties it (and
deletes the file) regardless of the setting. Entries are ranked by
frecency — a visit count that halves every three days — and at most 1,000
are kept; the ranking has no reader in the app yet.
DirectoryHistory.projectRoot(for:) separately finds the nearest ancestor
directory containing .git, for jumping to a project root rather than
wherever inside it a command happened to run. A directory only ever enters
this history already local — OSC 7's own host check
(Performer+OSC.swift) drops a remote report before TerminalSession .currentDirectory ever reports it, so nothing here can rank a path that
belongs to a different machine.
An app-initiated directory change (PaneCommands.changeDirectory(to:))
writes cd '<path>' to the child only when
ViewController.canChangeDirectorySafely holds: shell integration is
active, no command is currently running, and the cursor is still exactly
where the prompt finished drawing — so a directory a person picked from
history never lands on a busy shell, a TUI holding the
screen, or a prompt with something already typed into it.
Updates
| Key | Values | Default | Notes |
|---|---|---|---|
update-auto-check | boolean | true | Whether Corta checks appcast.xml in the background, once a day. |
suggest-applications-folder | boolean | true | Whether Corta offers to move itself into /Applications on launch, when it is not already there. |
Check for Updates… (the Corta menu, directly under About) always works,
on or off — this key only gates the unattended check nobody asked for.
Updates are fetched over HTTPS and verified against an EdDSA signature
before installing (UpdateController.swift, docs/DESIGN.md — Sparkle is
the one third-party dependency in the app shell; the terminal core has
none).
suggest-applications-folder turns itself off the first time the prompt
is answered either way — accepting moves the app and there is nothing
left to ask about, and "Don't Ask Again" writes false directly
(ApplicationsFolderMover.swift). New direct downloads use a disk image with an Applications link (D26).
The prompt still helps when an app is launched from the image or another
folder without being copied first; Sparkle and Spotlight/Launchpad expect
/Applications.
3. A complete example
Every scalar key, at its default unless the comment says otherwise. The structured families — themes (§4), presets (§4a) and shortcuts (§5) — are shown in their own sections.
# Appearance
theme = corta
appearance = auto
font-family = system
font-family-cjk = auto
font-size = 14 # default 13
font-weight = regular
line-spacing = 1.18
font-fallback = true
emoji-rendering = color
cursor-shape = block
cursor-blink = false
status-bar = false
status-items = cpu,load,memory,network,disk,thermal
status-network-interface = auto
# Window
columns = 120
rows = 30
restore-windows = true
confirm-close = true
# Terminal
scrollback-lines = 50000 # default 10000
ambiguous-width = narrow
command-history-limit = 512
bell = visual
input-source-indicator = auto
input-source-indicator-position = toolbar
input-source-direct-color =
input-source-ime-color =
option-as-meta = false
open-file-command =
search-regex = false
search-case-sensitive = false
paste-warning = dangerous
copy-on-select = true
mouse-override-modifier = option
link-activation = command
allow-clipboard-write = false
secure-keyboard-entry = false
# Notifications
notify-from-programs = true
agent-status = true
agent-processes =
notify-on-long-task = true # default false
notification-threshold = 60 # default 30
# History
directory-history = true
directory-completion = true
history-suggestions = true
command-status-marks = true
# Updates
update-auto-check = true
suggest-applications-folder = true
4. Themes
A theme is the sixteen ANSI colours plus the three the terminal owns (default foreground, default background, cursor), in a light and a dark variant. The 6×6×6 colour cube and the 24-step greyscale ramp are not part of a theme: xterm defines them numerically, so a program asking for colour 137 means one specific colour.
Corta offers three previewable themes: corta, solarized and mono.
Settings → Themes lists them once for light mode and once for dark mode,
and each mode keeps its own choice (theme = light:solarized,dark:corta).
Existing custom selections and inheritance continue to work.
Keys
| Key | Meaning |
|---|---|
theme.<name>.name | The display name in the Settings page and the View menu. Defaults to <name>. |
theme.<name>.inherit | A built-in to start from: corta, solarized, mono. Defaults to corta. |
theme.<name>.<variant>.foreground | Default text colour. <variant> is dark or light. |
theme.<name>.<variant>.background | Default background. |
theme.<name>.<variant>.cursor | Cursor colour, for every cursor style. A block cursor is opaque, and the character under it is drawn in the background colour, as Terminal.app draws it. |
theme.<name>.<variant>.ansi | The whole table on one line, comma-separated. A shorter list overrides a prefix of it. |
theme.<name>.<variant>.ansi<N> | One slot, N from 0 to 15: black, red, green, yellow, blue, magenta, cyan, white, then the eight bright ones. |
Colours are #rgb or #rrggbb; the # is optional. There is no alpha
component — the terminal surface is opaque content, not a glass layer.
Anything left unset is inherited, so a two-line theme is a legal theme — and a half-written one still renders, which matters because this file is hand-edited.
Derived colours
The highlights drawn over the text have no keys: they are worked out from the variant's own colours, so a light theme gets a selection that shows on a light background and a custom theme needs nothing extra. Fills sit under the characters, which keep their own colour.
- Selection: blue (
ansi4) moved 40% of the way towards the foreground, at 40% opacity. - Search matches: yellow (
ansi3) at 35% opacity; the current match at 55%. - Hovered link: an underline in cyan (
ansi6). - Command marks: green (
ansi2) at 85% opacity for a command that succeeded, red (ansi1) at 90% for one that failed, and the midpoint of the foreground and background at 90% for one that was interrupted.
Only the first eight slots are used: in some themes, Solarized among them, the bright slots are grey tones rather than brighter hues.
theme = midnight
theme.midnight.name = Midnight
theme.midnight.inherit = solarized
theme.midnight.dark.background = #101018
theme.midnight.dark.cursor = #00c2c7
theme.midnight.dark.ansi1 = #ff5f56
Graphical theme editing
View → Theme editor opens the editor directly. Settings → Appearance → Create theme copies the selected theme into an unsaved draft. Edit dark and light variants using color pickers or HEX input; the preview is local to the editor. Save theme writes a new custom theme and selects it. Edit theme is offered for custom themes; built-ins are copied rather than modified. Cancel discards the draft. Restore source colors restores the current variant to the colors it had when the editor opened. Invalid colors disable saving. An external edit to the same custom theme requires reopening the editor before saving.
4a. Presets
A preset is a named way to open a terminal: a shell, a directory and a few environment variables. Shell ▸ New Pane with Preset lists them, in the order the file defines them, and hold ⌥ to open one in a window of its own. With no presets defined the row is hidden rather than greyed out: a submenu's parent item carries no action, so AppKit enables it unconditionally and a disabled-looking row would still open an empty menu.
preset.api.shell = /bin/bash
preset.api.arguments = -l
preset.api.directory = /Users/you/src/api
preset.api.env.API_ENV = staging
preset.api.env.NO_COLOR = 1
Holding ⌥ while choosing a preset opens it in a window of its own instead of splitting the focused pane.
Every field is optional. shell and directory must be absolute — a
relative path would resolve against whatever Corta was launched from, which on
a Finder launch is / — and a preset that names neither an absolute path nor
any setting at all is ignored rather than offered.
env.* variables are added on top of the sanitised inherited environment
(SECURITY.md §4.3): a preset can add and override, never remove. A preset
whose shell has been uninstalled degrades down the same fallback ladder an
ordinary pane uses, so it opens a working terminal rather than a failure
panel — and the preset's arguments apply only to its own shell, since a
fallback /bin/sh may not understand them.
A preset is also the supported way to open a remote terminal:
preset.staging.shell = /usr/bin/ssh
preset.staging.arguments = staging.example.com
The child is the system's own OpenSSH client, so everything its
configuration does — Host blocks, Include, Match, ProxyJump, agent
keys, ControlMaster connection sharing — applies exactly as it does in any
other terminal; Corta parses none of it. directory, if set, remains a
local working directory for the launcher. The pane's title carries a Remote:
badge with the remote host and directory once the remote shell reports them
(OSC 7), and says so when it cannot know them. When the connection dies,
Shell ▸ Terminal and Connection ▸ Reconnect to Host (reconnect-remote) starts a new connection to
the same host — explicitly a new one, never a restore of the dead session.
A preset is applied once, at spawn. A pane opened from one is an ordinary
pane afterwards: there is nothing to leave and nothing to keep in sync. Presets
deliberately carry no colours, fonts or keybindings — those are window- or
app-wide in Corta (DESIGN.md §6), and a per-preset copy would be a second
settings store arguing with this file.
5. Keyboard shortcuts
bind.<command> = cmd+shift+d
bind.<command> = # an empty value unbinds
Modifiers: cmd/command, ctrl/control, alt/opt/option,
shift. Joined to the key with +, case-insensitive, in any order —
Corta writes a shortcut back as ctrl+alt+shift+cmd+key, which is the
spelling the table below uses.
Keys: any single character (d, ,, =, +), or one of the named
keys up, down, left, right, home, end, pageup, pagedown,
return, enter, tab, space, escape, delete.
Unbinding is not the same as restoring the default: an empty value leaves the command with no key, which is what you want when a TUI needs one Corta was taking.
An unbound keystroke is passed to the child process, encoded like any
other key Corta does not claim — it is not swallowed. That is the point of
unbinding: bind.close = is how ⌘W stops closing the pane and starts
reaching the program running in it. It also means unbinding is uniform —
a key Corta never bound and a key you unbound behave identically — and
that nothing can end up doing nothing, which reads as a bug rather than as
a setting. The command itself stays reachable from its menu and from the
command palette (⇧⌘P); Help ▸ Keyboard Shortcuts lists it as Not Set.
Nothing rejects two commands sharing one keystroke. Where AppKit decides, the first matching menu item in menu-bar order wins; where Corta decides, the first command in the table below wins. Help ▸ Keyboard Shortcuts shows the key against both rows, which is how you spot it.
The commands
bind. key | Command | Default |
|---|---|---|
new-window | New Window | cmd+n |
new-tab | New Tab | cmd+t |
previous-tab | Previous Tab | shift+cmd+[ |
next-tab | Next Tab | shift+cmd+] |
rename-tab | Rename Tab… | (none) |
select-tab-1 | Show Tab 1 | cmd+1 |
select-tab-2 | Show Tab 2 | cmd+2 |
select-tab-3 | Show Tab 3 | cmd+3 |
select-tab-4 | Show Tab 4 | cmd+4 |
select-tab-5 | Show Tab 5 | cmd+5 |
select-tab-6 | Show Tab 6 | cmd+6 |
select-tab-7 | Show Tab 7 | cmd+7 |
select-tab-8 | Show Tab 8 | cmd+8 |
select-last-tab | Show Last Tab | cmd+9 |
close | Close | cmd+w |
split-right | Split Pane Right | cmd+d |
split-down | Split Pane Down | shift+cmd+d |
focus-left | Move Focus Left | alt+cmd+left |
focus-right | Move Focus Right | alt+cmd+right |
focus-up | Move Focus Up | alt+cmd+up |
focus-down | Move Focus Down | alt+cmd+down |
grow-pane-horizontally | Grow Pane Horizontally | ctrl+cmd+right |
shrink-pane-horizontally | Shrink Pane Horizontally | ctrl+cmd+left |
grow-pane-vertically | Grow Pane Vertically | ctrl+cmd+down |
shrink-pane-vertically | Shrink Pane Vertically | ctrl+cmd+up |
zoom-pane | Zoom Pane | shift+cmd+return |
reopen-closed-pane | Reopen Closed Pane | shift+cmd+t |
equalize-panes | Equalize Panes | (none) |
increase-font-size | Bigger | cmd+= |
decrease-font-size | Smaller | cmd+- |
reset-font-size | Actual Size | cmd+0 |
find | Find… | cmd+f |
copy | Copy | cmd+c |
paste | Paste | cmd+v |
select-all | Select All | cmd+a |
scroll-page-up | Scroll Page Up | shift+pageup |
scroll-page-down | Scroll Page Down | shift+pagedown |
scroll-to-top | Scroll to Top | shift+home |
scroll-to-bottom | Scroll to Bottom | shift+end |
previous-failed-command | Previous Failed Command | shift+cmd+up |
next-failed-command | Next Failed Command | shift+cmd+down |
copy-last-command-output | Copy Last Command Output | (none) |
snapshot-running-command-output | Snapshot Running Command's Output | (none) |
export-command-output | Export Command Output… | (none) |
open-file-reference-in-command | Open File Reference in Command Output | (none) |
search-command-history | Search Command History… | (none) |
reveal-working-directory | Reveal Working Directory in Finder | (none) |
copy-working-directory-path | Copy Working Directory Path | (none) |
change-directory-to-parent | Change Directory to Parent | (none) |
change-directory-to-project-root | Change Directory to Project Root | (none) |
open-parent-directory-in-new-pane | Open Parent Directory in New Pane | (none) |
open-project-root-in-new-pane | Open Project Root in New Pane | (none) |
export-text | Export Text… | shift+cmd+s |
clear-screen | Clear Screen | cmd+k |
clear-history | Clear History | (none) |
reset-terminal | Reset Terminal | (none) |
reconnect-remote | Reconnect to Host | (none) |
browse-remote-files | Browse Remote Files… | (none) |
previous-command | Previous Command | cmd+up |
next-command | Next Command | cmd+down |
secure-keyboard-entry | Secure Keyboard Entry | (none) |
settings | Settings… | cmd+, |
command-palette | Command Palette… | shift+cmd+p |
previous-command, next-command, the two failed-command jumps,
copy-last-command-output, snapshot-running-command-output,
export-command-output and open-file-reference-in-command all need
shell integration (OSC 133) to have anything to work with; without it their
menu items are disabled rather than silent, and search-command-history
opens a window with nothing in it. copy-last-command-output uses the
OSC 133 ; C output-start mark and the next prompt boundary, so a multiline
prompt or continued command is excluded from the output. Integration that
emits no C mark falls back to one row after the prompt; only that fallback
can include part of a multiline prompt or command.
snapshot-running-command-output is copy-last-command-output's answer for
a command that has not finished yet: copy-last-command-output needs the
OSC 133 ; D mark that only arrives at the end, so it finds nothing while
something is still building. This instead copies everything the running
command has printed so far, with a header naming when the snapshot was taken
— because what a build has printed at minute three is still worth reading,
and waiting for it to finish to read it would be Corta making the user wait
on itself.
export-command-output is copy-last-command-output written to a file
instead of the clipboard, for a build log too long to want pasted anywhere
but still worth attaching to a bug report. open-file-reference-in-command
opens the last path:line[:column] reference in that command's output
through open-file-command (§2), without hunting through the scrollback for
it by eye. Both act on the command a jump (or a notification's click) landed on,
else the one whose prompt is nearest the top of the viewport, else the
last one that finished. search-command-history opens the Command History window for the
pane — every recorded command with its time, exit status and directory,
searchable, with Fill and Run for a command whose text is still in
the scrollback (command-history-limit bounds how many are kept). Run only
executes a complete visible single-line command of at most 80 characters
without tabs; other commands are filled at the prompt for you to inspect
and submit with Return.
The six working-directory commands act on the directory the pane's shell
last reported through OSC 7, and are disabled until it has reported one.
reveal-working-directory selects it in Finder and
copy-working-directory-path puts the path on the clipboard.
change-directory-to-parent and change-directory-to-project-root write a
cd to the shell under the same safety gate every app-initiated directory
change uses (§2, History): only when no command is running and the prompt is
empty. The project root is the nearest ancestor containing .git. The two
project-root commands look for it after you choose them — off the main
thread, since the path is the shell's and may sit on a slow or unreachable
volume — and show "No project root" when there is none, rather than being
disabled ahead of time.
open-parent-directory-in-new-pane and open-project-root-in-new-pane
split the pane with a new local one rooted there instead. In a remote pane
only change-directory-to-parent works — the cd goes to the remote shell,
where the path lives — while the Finder, clipboard, project-root and
new-pane commands stay disabled, because none of them may act on a path
that belongs to another machine (SECURITY.md §2.8).
reopen-closed-pane puts a closed pane back where it was — same split, same
side, same divider, same working directory. It restores the arrangement,
never the process: the child that was running is gone, and its scrollback with
it. The record is one pane deep, because the position of anything older is
described against a tree the first reopen has already changed.
reconnect-remote appears only for a pane whose recorded spawn command was a
remote launcher (ssh, mosh) and whose child has exited. It re-runs exactly
that command — no fallback ladder, because landing in a silent local shell
when you asked for the host back would look like success and be nothing of
the kind. It is a new connection: the copy says so, and never suggests
the dead remote shell or its scrollback came back. When the recorded command
itself attaches to a remote multiplexer (tmux attach, screen -r), the
copy says the command reattaches to whatever session exists on the host —
the reattach is the remote program's doing, not Corta's.
browse-remote-files opens the SFTP browser for the focused pane's host —
enabled only for a pane known to be remote. The host name comes from the
remote shell's own report, which is program output, so the first
connection to a host in each run is a question: the browser opens with the
name prefilled and editable (correct it to the alias you would pass to
ssh), and connects only when you press Connect; a ⌘-clicked file
reference in a remote pane asks the same question once. The channel is the
system's ssh -s -- <host> sftp, so authentication and ~/.ssh/config
belong to OpenSSH; there is nothing to configure here. Like sftp(1) and
scp(1), the channel never forwards the agent, X11 or ports, never runs a
LocalCommand or a RemoteCommand, never asks for a remote terminal and
never becomes a ControlMaster, whatever the configuration says for that
host (it still reuses a master your terminal opened). It has no terminal,
so ssh asks for a password, a key passphrase or a new host key through
Corta, in a dialog; nothing is saved.
(CORTA_SFTP_SSH, an environment variable and never a config key, names
an absolute path to run instead of /usr/bin/ssh with the same argv — a
verification hook for driving a Debug build only against a local
sftp-server; Release and Benchmark ignore it. See
environment switches.) Transfers use a
.corta-part partial committed to the destination on completion, resumable with both
endpoints re-validated, and every overwrite is a decision you make in a
sheet, not a default. A remote file's Edit action — or ⌘-clicking a
path:line reference in a remote pane — downloads a managed copy, opens it
through open-file-command, and treats uploading your edits back as an
explicit question, re-checking the remote for changes first.
export-text writes the selection — or, with nothing selected, the whole
scrollback and screen — to a text file. It is the same text ⌘C would put on
the clipboard, including how a soft-wrapped line is joined.
zoom-pane fills the window with the focused pane and the same key puts the
split back. It is temporary and changes nothing: no pane is closed, no child
process is disturbed, and the saved arrangement still describes the splits,
not the zoom. The menu item names whichever direction it will go next.
The three terminal-state commands are separate because no two terminals mean the same thing by "clear", and each says what it discards:
| Command | Screen | Scrollback | Modes, colours, cursor |
|---|---|---|---|
| Clear Screen | erased; ready shell editor kept | kept | kept |
| Clear History | kept | discarded | kept |
| Reset Terminal | erased | discarded | reset |
At a ready integrated prompt, clear-screen relocates the complete displayed
prompt and input to the top, preserving the cursor position within that input,
cell attributes and wrapping. It needs this prompt's end marker (OSC 133 B);
there is no prompt-text guessing or synthetic Return/Control-L. Without a ready
prompt it retains the ordinary erase-and-home behavior. On the alternate
screen it does nothing; an editor whose prompt starts in scrollback is also
left intact. Scrollback is never appended or discarded by this command.
All three act on Corta's own grid, not on the program running in it: nothing
is written to the child's input, so a running job is undisturbed and redraws
on its next frame. clear-history and reset-terminal ship unbound — both
throw history away, and a key that discards a build log by accident is not a
default — and both ask before discarding, naming how many lines are at stake.
The question is skipped when the scrollback is empty, and suppressed entirely
by confirm-close = false, which is the existing key for "ask me before I
lose work".
Every command in this table is also in the command palette (⇧⌘P), which lists the same table — so a command with no default binding is still one search away.
6. What is deliberately not configurable
TERM. Alwaysxterm-256color(DECISIONS.mdD08).- Reading the clipboard from the terminal. The OSC 52 read form is
not implemented and will not be (
SECURITY.md§6). - Title queries. Terminal programs cannot read back the window title. Colour queries are supported: OSC 4/5 report indexed/special colours, and OSC 10/11/12 report foreground, background and cursor colours. These are protocol responses, not settings switches.
- Transparency, ligatures, and per-profile settings. Not shipped; each needs a decision in DECISIONS.md before it is a feature — propose one in Discussions → Ideas.
7. Where the values are applied
| Change | Takes effect |
|---|---|
theme, appearance, font-family, font-family-cjk, font-weight, line-spacing, font-fallback, emoji-rendering, cursor-shape, cursor-blink | Immediately, in every open pane; a change to the cell's size refits the rows and columns to the window. |
ambiguous-width | Immediately, for output written afterwards; text already on screen keeps its width. |
font-size | Immediately in panes without temporary font zoom; zoomed windows keep their temporary size until Actual Size (⌘0). New windows use the configured size. |
bell, option-as-meta, search-case-sensitive, search-regex, open-file-command, paste-warning, copy-on-select, mouse-override-modifier, link-activation, allow-clipboard-write, confirm-close, directory-history, notification keys | Immediately — they are read when the behaviour happens. |
status-bar, status-items, status-network-interface, input-source indicator keys | Immediately; polling follows visible enabled bars. |
command-status-marks | Immediately; changes the command-outcome overlay without changing terminal text. |
directory-completion, history-suggestions | Disabling immediately hides suggestions and restores native key handling. Enabling requires a new shell to load automatic local zsh hooks; a live shell keeps hooks it already loaded. |
bind.* | Immediately: the menu key equivalents are re-applied on every file change. |
theme.* | Immediately, if the live theme is the one you edited. |
columns, rows | The next window opened. |
scrollback-lines | Sessions started afterwards; a running shell keeps the history it has. |
command-history-limit | Sessions started afterwards, same reason as scrollback-lines. |
restore-windows | The next launch. |
secure-keyboard-entry | Immediately, whenever a terminal window is key. |
update-auto-check | Immediately — applied to the live Sparkle updater on every file change. |
suggest-applications-folder | The next launch. |
8. Config file vs. app-owned state
This file holds settings: values a person chose and might reasonably
hand-edit, review in version control, or copy to another machine. Files under
~/Library/Application Support/Corta/ hold app-owned state, history and
managed remote-edit copies. Their contents are not configuration keys.
| Store | File | What it holds | Category |
|---|---|---|---|
Configuration | ~/.config/corta/config (this file) | Appearance, terminal behaviour, notifications, shell integration and history toggles, updates | Global preference — applies to every window and pane until changed again |
Configuration.columns/.rows | same file | The grid a new window opens with | Window-launch default — read once, at the moment a window is created; changing it does nothing to windows already open |
Configuration.presets | same file | Named shell/directory/environment combinations | Pane-launch configuration — read once, at the moment a pane spawns from that preset; never re-applied afterwards (docs/CONFIGURATION.md §4a) |
SessionRestore | state.json | Window frames and split arrangement, restored on the next launch | Window/session state — describes this run's windows, not a preference about future ones |
DirectoryHistoryStore | directory-history.json | Ranked recent directories | Temporary interaction history — gated by directory-history; clear it in Privacy & Security ▸ History |
RecentHostsStore | recent-hosts.json | Recently connected SSH/SFTP hosts | Connection history — clear it in Privacy & Security ▸ Recent hosts |
RemoteEditStore | RemoteEdit/manifest.json and managed copies beneath RemoteEdit/ | Remote baselines, local edits and approved-upload snapshots | Managed editing state — preserve undecided edits before deleting app data; used copies are pruned after 30 days unless edits are pending |
Why separate stores: Configuration is line-oriented text
specifically so a person can open it in $EDITOR, diff it, and understand
every line; state.json and directory-history.json are JSON blobs a
person never opens, rewritten wholesale on every change, with no comments
or ordering worth preserving. Folding window arrangement or directory
history into the hand-edited file would make every window move or cd
churn the file a person is supposed to be able to read at a glance — the
same reason SessionRestore.swift's own header comment gives for keeping
state in Application Support rather than the config directory.
Both state.json and directory-history.json carry a version field
(added in B09) so a future, incompatible change to either format can skip
or migrate old data instead of the previous behaviour — silently discarding
the entire file the moment it fails to decode. Configuration has never
needed one: unknown keys are preserved verbatim (§1) and an unparsed line
is simply skipped, so it already degrades one line at a time rather than as
a whole file.
What each of these files may contain, and the modes they are written with, is the data-at-rest policy.
9. Local system status
CPU is the busy fraction of Mach CPU tick deltas between samples. Load shows 1-, 5-, and 15-minute averages. Memory is wired + non-purgeable anonymous + physical compressed pages, excluding file cache; it is not total minus free. Network rates use byte-counter deltas for exactly one interface, resetting the baseline on interface changes or pauses. Disk free is the available capacity of the volume containing the user's home directory, refreshed every 30 seconds. Thermal state is macOS's normal / elevated / high / critical classification, not a temperature in degrees. Metrics sample every two seconds off the UI thread, and all windows share one sampler. These are local-machine metrics, including while the selected pane is connected over SSH.
The bottom bar is 24 points high. It uses abbreviated labels and byte quantities with up to one decimal digit to fit more selected metrics. Hover or open details for full labels and precision; accessibility also receives the full text. Numbers follow the current locale, and labels follow the app's selected language. Host details are also available from View → Local host details or Settings → Terminal → Local host details, even with the status bar disabled.