Files
tty7/docs/reference/configuration.mdx
T
l0ng-ai cf0e8f02e6 feat(sidebar): pinned groups above auto groups (#962)
* feat(sidebar): keep pinned groups on the workspace and derive the rest

Replace the sidebar's hand-made groups with the model from #955: the sidebar
groups tabs by repo automatically, and you pin what you want to keep.

- Pinned groups (`PinnedGroup`: id, optional name, optional folder, fold) are
  stored on the workspace in the machine tree, in display order, and a tab
  points at one by `GroupId`. Everything else is an auto group worked out
  every frame and never stored: by repo home, or by `user@host` for an SSH
  pane — native or a shell that ssh'd onward — so `/home/ubuntu` on two
  machines no longer lands under one header.
- A tab whose cwd *enters* a pinned folder joins it (deepest folder wins; a
  repo home equal to the folder counts, which keeps worktrees with their
  repo). It is edge-triggered through `EntryWatch`, so a tab dragged out
  while still inside the folder stays out until it leaves and comes back, and
  a tab restored at launch is not pulled in by where it already sits.
- Groups sync as one `WorkspaceSetGroups` / `GroupsChanged`, pushed only from
  an edit and adopted from every pull, so a fresh window can never push an
  empty set over the workspace's. The machine hands tabs naming a dropped
  group back to auto grouping in the same mutation. Control dialect → v11.
- Config: `sidebar_grouping` (three modes) and `sidebar_collapsed_groups` give
  way to one `sidebar_auto_grouping` toggle; folds live with the workspace.
- `tty7 tab ls` reports the pinned group a tab is in (name or folder leaf;
  JSON carries id, name and folder).

* feat(sidebar): draw pinned groups above a divider, with their own gestures

The sidebar now reads as two halves: the groups you keep, in the order you
put them, then a divider, then the groups it works out (Arc-style).

- Pinned headers drag-reorder among themselves (their own reorder surface, so
  a pinned header cannot be dropped among the derived ones); the order lands
  on the workspace's group list, not on the tabs.
- An auto header carried above the divider is pinned when let go. With
  nothing pinned yet the divider appears during that drag as a "Drop here to
  pin" zone, since a hairline at the top of the list is nothing to aim at.
- A tab kept in a pinned group and dropped anywhere below the divider goes
  back to auto grouping; the divider lights to say so.
- An empty pinned group stays, with a "+ New Tab" row that opens a tab in its
  folder (or where ⌘T would, for a label group) and files it there.
- Folder groups carry a pin mark that unpins on click and a tooltip with the
  folder; auto headers show pin and "+" on hover.
- Header menus: pinned — Rename, Set Folder… (local workspaces), Use Current
  Tab's Folder, Clear Folder, New Tab, Unpin (folder groups), Delete. Auto —
  Pin Group, New Tab. Nothing renames an auto group; nothing pins implicitly.

* feat(sidebar): open folders as pinned groups from Finder, the file tree and the palette

Every way into a pinned group the design calls for:

- Drop a folder from Finder or Explorer onto the sidebar to pin it (a local
  workspace only — a dropped path is this machine's, and a folder group keeps
  a directory on the workspace's host). Files are let fall.
- "Pin as Group" on a folder in the file tree, on local and remote workspaces
  alike, since the tree and the group are both on the workspace's host.
- Palette "New Group" makes an empty label group and opens its name for
  typing; "Open Folder as Group…" picks a folder with the system picker, pins
  it and opens a tab in it. The picker browses this computer, so that one is
  not offered on a remote workspace.
- Tab right-click "Move to Group" lists the pinned groups plus "New Group…",
  which files the tab in a fresh label group with its name open for typing.

Pinning a folder already pinned hands back the group that keeps it rather
than making a second one to split its tabs with.

* fix(sidebar): let groups that arrive from elsewhere pull no tab into a folder

A window draws its first frames before its copy of the workspace's groups
lands, so every tab's entry watch recorded "in no folder" — and the groups
landing then read as each tab walking into its folder. A restored tab, or one
dragged out of its folder group, was pulled back in on every launch.

Groups adopted from a pull or from another window's `GroupsChanged` now start
every tab's watch over from where it is; only this window's own pin gathers
the tabs inside the folder, and says so tab by tab. A tab also goes up with
the group it names even when the window does not know that group yet, so a
sync in that same gap cannot send every kept tab back to auto grouping.

* docs(sidebar): describe pinned and auto groups, and log the change

Rewrite the sidebar page's grouping section around "grouped by repo
automatically; pin what you want to keep": the divider, folder and label
groups, the edge-triggered join, every way to pin, and the header menus. The
configuration reference swaps `sidebar_grouping` for `sidebar_auto_grouping`,
the CLI reference describes the GROUP column as the pinned group, and the
changelog gains an Unreleased entry (#955).

* fix(sidebar): file a tab opened by the CLI in a pinned folder into it

A tab that reaches a window as TabCreated — from `tty7 tab new` or another
window — started its entry watch as a restored tab, so opening one inside a
pinned folder left it in the auto group below. It is as new as a tab opened
here, and now joins the folder like one; every window that hears of it
reaches the same answer.

* test(machine): build the group sets in their initializers

Clippy's field_reassign_with_default on the two WorkspaceGroups the
set-groups test assembles.

* fix(sidebar): draw restored tabs in their auto group, and title by repo again

Auto groups are not stored, so after a restart every tab sat in Ungrouped
until its own repo probe came back, then jumped; before pinned groups the
stored repo key put it in place on the first frame. Each tab now carries
`last_auto`, the auto group it last resolved to, as a hint: stored with the
tab, sent up alongside its group in `TabSetGroup` whenever the live answer
moves, and used to draw the tab until the probe answers. The probe always wins
and rewrites the hint, and the hint never outranks a pinned group or the
folder-entry rule. Another window's hint only fills a gap, so two windows can
never bounce a disagreement between them.

The workspace's fallback title regained the repo majority it lost: the most
common pinned folder first, then the repo most unpinned tabs were last filed
under (a worktree counting toward its repo home), then a pane's cwd.

* refactor: drop what the new sidebar left unused, and two clippy findings

- `TerminalView::native_ssh_cwd` and its helper existed for the sidebar's old
  folder grouping of native SSH panes; an SSH tab now groups by host, and
  nothing else read it.
- The file tree's context menu takes `cx` instead of `danger` and the new
  groups flag, back to the argument count it had on main.
- A title test builds its workspace in the initializer.
2026-09-25 16:53:26 +08:00

242 lines
14 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
title: "config.json"
description: "Every key tty7 reads, its type, and its default."
---
| | |
|---|---|
| macOS / Linux | `~/.config/tty7/config.json` |
| Windows | `%APPDATA%\tty7\config.json` |
| Windows, portable mode | `<folder of tty7-app.exe>\data\config.json` |
| Override the whole directory | `TTY7_CONFIG_DIR`, or `--config-dir` |
Every key is optional — a missing one means its default, so you only write what
you change. Out-of-range numbers are clamped rather than rejected, and an
unrecognised enum value falls back to the default with a log line instead of
failing the file.
```json
{
"font_family": "JetBrains Mono",
"font_size": 14,
"theme_follow_system": true,
"theme_preset_light": "one_light",
"theme_preset_dark": "dracula",
"macos_option_as_alt": true,
"scrollback_limit": 50000
}
```
## Where the directory comes from
The config directory holds everything an instance keeps, not just
`config.json`: themes, sessions, scrollback, history, and the background
server's socket. The first of these wins:
1. `--config-dir <dir>` on the command line — `tty7-app.exe --config-dir D:\tty7-data`,
or `tty7-app --config-dir ~/tty7-alt` elsewhere. The app hands it on to the
server it starts, so the whole instance moves.
2. The `TTY7_CONFIG_DIR` environment variable. Unlike a flag it also reaches
launches you do not control — Explorer's **Open in tty7**, a desktop file,
the `tty7` command in another terminal.
3. **Windows portable mode**: a `data` folder next to `tty7-app.exe` in an
unzipped portable ZIP (the one carrying a `.tty7-portable` file). See
[Installation](/getting-started/installation).
4. The platform default in the table above.
Two directories are two independent instances, each with its own server and
sessions.
## Typography
| Key | Type | Default | |
|---|---|---|---|
| `font_family` | string | `"Hack"` | Primary face. Hack is bundled. |
| `font_fallbacks` | string[] | platform list | Ordered fallbacks. Stock platform faces are appended to whatever you write. |
| `font_family_bold` | string | — | A distinct bold face. |
| `font_family_italic` | string | — | A distinct italic face. |
| `font_features` | object | — | OpenType tags, e.g. `{"calt": true, "liga": 1}`. Four alphanumeric characters per tag. |
| `font_thicken` | bool | `true` | macOS: let font smoothing thicken strokes, light text most. `false` draws glyphs at their own weight. Applies after a restart. |
| `font_size` | number | `15` | Terminal text size in px (4–256). |
| `line_height` | number | `1.4` | Multiple of the font size (0.5–4). |
| `ui_font_size` | number | `16` | The interface's root size in px (12–24). |
| `ui_font_family` | string | — | The face for everything outside the grid. Unset means the system UI font. |
[More about fonts →](/customization/fonts)
## Theme and window
| Key | Type | Default | |
|---|---|---|---|
| `theme_preset` | string | `"light"` | Active theme id. |
| `theme_follow_system` | bool | `false` | Follow the OS appearance. |
| `theme_preset_light` | string | `"light"` | Used when following the system. |
| `theme_preset_dark` | string | `"dark"` | Used when following the system. |
| `theme_legible_palette` | bool | `true` | Brighten or darken bright ANSI colours that would be unreadable on the background. |
| `window_opacity` | number | — | 0.2–1.0. Unset means "follow the theme". |
| `window_blur` | bool | — | Blur behind a translucent window (macOS). Unset means "follow the theme". |
| `window_backdrop` | enum | `"auto"` | Windows only: `auto`, `blur`, `mica`, `mica-alt`, `acrylic`, `off`. |
| `dim_inactive_panes` | bool | `true` | Dim panes that are not focused. |
| `startup_mode` | enum | `"normal"` | `normal`, `maximized`, `fullscreen`. |
| `remember_window_size` | bool | `true` | Reopen at the last size and position. |
| `restore_session` | bool | `true` | Reopen the last window's tabs, splits, and directories. |
| `gui_language` | enum | `"en"` | `en`, `zh-CN`, `ja-JP`. Anything else falls back to `en`. |
Built-in theme ids: `light`, `one_light`, `catppuccin_latte`, `rose_pine_dawn`,
`dark`, `dracula`, `harbor`, `one_dark_pro`, `rose_pine`. Your own themes take
their id from the file name. [More about themes →](/customization/themes)
## Tabs, sidebar, panels
| Key | Type | Default | |
|---|---|---|---|
| `tab_bar_position` | enum | `"left"` | `left` (sidebar) or `top` (strip). |
| `new_tab_position` | enum | `"after-current"` | Or `end`. |
| `ssh_tab_title` | enum | `"dynamic"` | What an SSH tab is called: `dynamic` (the title the remote side sets), `profile-name` (the saved host's name, a `~/.ssh/config` alias, or the address typed for a quick connect), or `hostname` (the address dialled). A tab you renamed keeps its name. |
| `sidebar_auto_grouping` | bool | `true` | Groups unpinned tabs by repository (SSH tabs by host). Off: a flat list below the pinned groups, which show either way. Pinned groups and folds are stored with the workspace, not here. |
| `sidebar_diff_preview` | bool | `true` | Clicking a row's `+N −M` opens the diff overlay. |
| `sidebar_width` | number | `220` | Pixels (100–2000). |
| `sidebar_collapsed` | bool | `false` | |
| `right_panel_visible` | bool | `false` | |
| `right_panel_width` | number | `260` | Pixels (100–2000). |
| `right_panel_tab` | enum | `"info"` | `info`, `changes`, `files`. |
| `diff_view` | enum | `"split"` | Or `unified`. Global, not per file. |
| `document_layout` | enum | `"dock"` | Where an open file or diff is drawn: `dock` beside the terminal, or `fill` over the workspace. What a fresh tab starts as — each tab keeps its own from there. |
| `document_ratio` | number | `0.5` | The docked column’s share of the terminal column (0.2–0.8). Named widths are `0.333`, `0.5`, `0.667`. |
| `scm_graph_expanded` | bool | `false` | Whether the history section starts open. |
| `editor_soft_wrap` | bool | `false` | Whether files open in the code panel with soft wrap on. Follows the status bar's Wrap toggle — whatever it was last left at. |
| `editor_markdown_preview` | bool | `false` | Whether Markdown files open rendered rather than as source. Follows the Preview / Edit toggle the same way. |
| `show_tray_icon` | bool | `true` | The tray / menu bar status item. |
## Terminal
| Key | Type | Default | |
|---|---|---|---|
| `shell` | object | — | `{"program": "fish", "args": ["-l"]}`. Unset uses the platform default. `args` you write are launched verbatim, which also turns off [shell integration](/reference/shell-integration) for that shell — leave them out to keep it. |
| `custom_shells` | array | `[]` | Extra entries for the new-tab menu: `[{"label": "Ubuntu", "program": "wsl.exe", "args": ["-d", "Ubuntu"]}]`. Launched exactly as written, listed after the detected shells — in the menu's **Other Shells…** list until you have opened it, and in the palette as *Shell:* followed by its label. Give an entry arguments and tty7 also skips [shell integration](/reference/shell-integration) for it, so it has no prompt marks, working-directory tracking, or command-finished notifications; an entry with no arguments, on a shell tty7 recognizes, is integrated like the detected row beside it. An entry with no `program` is skipped; with no `label` it is named after its program. |
| `working_directory` | object | `{"strategy":"inherit"}` | `strategy` is `inherit`, `home`, or `custom`; `path` is used when custom. |
| `env` | object | `{}` | Extra environment variables for every pane. |
| `scrollback_limit` | number | `10000` | Lines per pane (100–100,000). New panes only. |
| `cursor_style` | enum | `"block"` | `block`, `bar`, `underline`. |
| `cursor_blink` | bool | `true` | |
| `bell` | enum | `"visual"` | `none`, `visual`, `audible`, `both`. |
| `per_pane_history` | bool | `false` | Give each pane its own shell history file. |
## Mouse and scrolling
| Key | Type | Default | |
|---|---|---|---|
| `mouse_scroll_multiplier` | number | `1.0` | 0.1–10. |
| `smooth_scroll` | bool | `true` | Ease each wheel notch. Trackpads unaffected. |
| `mouse_reporting` | bool | `true` | Let full-screen apps handle clicks and scrolling. |
| `mouse_zoom_modifier` | enum | `"platform"` | Modifier that makes the wheel resize the font: `platform` (⌘ on macOS, Ctrl elsewhere), `ctrl`, `alt`, `none`. |
| `mouse_hide_while_typing` | bool | `true` | |
| `focus_follows_mouse` | bool | `false` | |
## Input and clipboard
| Key | Type | Default | |
|---|---|---|---|
| `prompt_editor` | bool | `true` | tty7 edits the shell prompt. Off sends every key at the prompt to the shell, so its own line editor (ZLE, readline) owns editing. Shell integration stays on. |
| `tab_completion` | bool | `true` | tty7's completion menu on <kbd>⇥</kbd>. Off hands the key to the shell. Needs `prompt_editor`. |
| `history_search` | bool | `true` | tty7's fuzzy history on <kbd>⌃ R</kbd>. Off hands the key to the shell. Needs `prompt_editor`. |
| `smart_select` | bool | `true` | Double-click grabs URLs, paths, bracket pairs, CJK words. |
| `word_separators` | string | see below | Characters that end a word. Used when smart selection is off. |
| `copy_on_select` | bool | `false` | |
| `clipboard_trim_trailing_spaces` | bool | `false` | |
| `macos_option_as_alt` | bool | `false` | <kbd>⌥</kbd>+key sends the escape chord instead of typing a special character. |
| `keybindings` | object | `{}` | `{"SplitRight": "cmd-d"}`. [Syntax →](/customization/keybindings) |
| `keybinding_preset` | string | `"default"` | Or `"tmux"`. |
| `prefix` | string | `"ctrl-b"` | The tmux preset's prefix. |
The default `word_separators` are a comma, a box-drawing bar, a backtick, a
pipe, a colon, both quote characters, a space, the six bracket characters, the
angle brackets, and a tab:
```json
{ "word_separators": ",│`|:\"' ()[]{}<>\t" }
```
## Links
| Key | Type | Default | |
|---|---|---|---|
| `link_url` | bool | `true` | Underline and open URLs on ⌘/Ctrl-click. |
| `link_file_open` | string | `internal` | What a file link opens: `internal` (tty7's editor, at the line), `system` (the OS file association), `command`. A file on another machine always uses `internal`. |
| `link_file_command` | string | — | Command for file links under `link_file_open: command`. `{path}`, `{line}`, `{column}` are substituted; a flag whose value is missing is dropped. |
| `ssh_loopback_forward` | bool | `true` | Forward the ports a remote pane starts serving, and open its `localhost:PORT` links through those forwards. |
## Notifications
| Key | Type | Default | |
|---|---|---|---|
| `notify_on_command_finish` | enum | `"unfocused"` | `never`, `unfocused`, `always`. |
| `notify_threshold_secs` | number | `10` | How long a command must run to qualify (1–3600). |
## Agents
| Key | Type | Default | |
|---|---|---|---|
| `agent_commands` | object | `{}` | Map a wrapper command to an agent slug: `{"cc": "claude"}`. |
| `agent_launch` | object | `{}` | The command line quick launch types for an agent, by slug: `{"claude": "claude --dangerously-skip-permissions"}`. Unset agents launch as their bare binary. [More →](/agents/overview#quick-launch) |
| `agent_frecency` | object | `{}` | Written by tty7: how often and how recently each agent ran, to order quick launch. |
| `restore_agent_sessions` | bool | `true` | Relaunch an agent conversation when a lost pane is restored. |
| `install_cli_on_path` | bool | `true` | Put the bundled `tty7` command on PATH at launch. |
[Agent slugs →](/agents/overview#your-own-wrapper)
## SSH
| Key | Type | Default | |
|---|---|---|---|
| `verify_host_keys` | bool | `true` | |
| `ssh_warn_on_close` | bool | `false` | Confirm before closing a live connection. |
### servers.json
The saved hosts are not in `config.json`: they live in `servers.json` in the
same directory, so `config.json` can be synced between machines without taking
your list of servers along. The file is written with mode `0600` and read as
forgivingly as `config.json`; if it cannot be parsed, tty7 keeps a copy at
`servers.json.corrupt` and saves nothing until it is repaired.
| Key | Type | Default | |
|---|---|---|---|
| `ssh_profiles` | array | `[]` | Managed from **Settings → SSH**. Secrets live in the OS keychain, never here. |
| `ssh_profile_frecency` | object | `{}` | How often and how recently each host is used, for ranking. Written by the app. |
An older `config.json` that still holds `ssh_profiles` is split the first time
a newer tty7 reads it: `servers.json` is written first, and only then are the
two keys removed from `config.json`, leaving the rest of it as it was. If both
files hold hosts, `servers.json` wins, and the copy in `config.json` is dropped
the next time settings are saved.
Each object in `ssh_profiles` can also set:
| Key | Type | Default | |
|---|---|---|---|
| `remote_clipboard_write` | bool | `false` | Allow programs on that SSH host to write PNG, JPEG, GIF, or WebP images to this machine's clipboard with OSC 5522. |
## Updates and network
| Key | Type | Default | |
|---|---|---|---|
| `check_for_updates` | bool | `true` | |
| `update_channel` | enum | `"stable"` | Or `nightly`. |
| `auto_download_updates` | bool | `true` | Fetch and verify in the background so installing is a restart. Packages are ~25–30 MB and a check happens every six hours. |
| `http_proxy` | string | — | For tty7's *own* traffic only — update checks, downloads, remote-server installs. `http://…` or `socks5://…`. Programs in a pane are unaffected. |
[Updates →](/reference/updates)
## Keys tty7 manages itself
`shell_frecency` and `command_frecency` record how often and how recently you
open a shell or run a command, so the new-tab menu and the palette can rank
them. They are written by the app; there is no reason to edit them.
<Note>
If the file cannot be parsed, tty7 starts on defaults, keeps your original at
`config.json.corrupt`, and logs the reason. It never silently overwrites what
you wrote.
</Note>