diff --git a/README.md b/README.md
index c9430037..0ba8a115 100644
--- a/README.md
+++ b/README.md
@@ -54,8 +54,11 @@ Native builds for each platform on [**Releases**](https://github.com/l0ng-ai/tty
| **CLI + Skills** | bundled `tty7` CLI · [agent skill](skills/tty7/SKILL.md) · pane/workspace control · real PTY commands · output, process, port, and agent status |
| **SSH** | native russh stack: profiles with keychain secrets · SFTP panel · port forwarding · jump hosts · one-time, unprivileged `tty7-server` install |
-Terminal and keybinding reference: [docs/features.md](docs/features.md). The agent-facing CLI
-interface is documented in [skills/tty7/SKILL.md](skills/tty7/SKILL.md).
+Full documentation lives in [**`docs/`**](docs/) —
+[keyboard shortcuts](docs/reference/keyboard-shortcuts.mdx) ·
+[config.json](docs/reference/configuration.mdx) ·
+[CLI reference](docs/cli/reference.mdx). The agent-facing CLI interface is also
+documented in [skills/tty7/SKILL.md](skills/tty7/SKILL.md).
Install the skill with:
diff --git a/README.zh-CN.md b/README.zh-CN.md
index e7ca60fd..56a5ab7a 100644
--- a/README.zh-CN.md
+++ b/README.zh-CN.md
@@ -54,7 +54,10 @@
| **CLI + Skills** | 安装包自带 `tty7` CLI · [agent skill](skills/tty7/SKILL.md) · pane/工作区控制 · 真实 PTY 命令 · 输出、进程、端口和 agent 状态 |
| **SSH** | 原生 russh 栈:profile 凭据进 keychain · SFTP 面板 · 端口转发 · 跳板机 · 一次无 sudo 安装 `tty7-server` |
-终端和快捷键参考:[docs/features.zh-CN.md](docs/features.zh-CN.md)。面向 agent 的 CLI 接口见
+完整文档在 [**`docs/`**](docs/)(英文)——
+[快捷键](docs/reference/keyboard-shortcuts.mdx) ·
+[config.json](docs/reference/configuration.mdx) ·
+[CLI 参考](docs/cli/reference.mdx)。面向 agent 的 CLI 接口另见
[skills/tty7/SKILL.md](skills/tty7/SKILL.md)。
通过以下命令安装 skill:
diff --git a/docs/agents/orchestration.mdx b/docs/agents/orchestration.mdx
new file mode 100644
index 00000000..eae1acc6
--- /dev/null
+++ b/docs/agents/orchestration.mdx
@@ -0,0 +1,103 @@
+---
+title: "Orchestrating agents"
+description: "One agent opening a pane for another, waiting on it, and reading the result."
+---
+
+Once an agent's status is a thing a program can ask about, one agent can run
+another. tty7 gives that loop a primitive instead of leaving it to screen
+scraping.
+
+## The loop
+
+```bash
+# 1. give the worker a pane
+PANE=$(tty7 split --v)
+
+# 2. hand it a task
+tty7 send "$PANE" 'claude -p "add tests for the parser"' --enter
+
+# 3. sleep until it needs you or finishes
+tty7 wait "$PANE" --until waiting,done --changed --timeout 600
+
+# 4. read what happened
+tty7 capture "$PANE" --plain
+
+# 5. clean up
+tty7 pane close "$PANE"
+```
+
+That is the whole shape. The interesting step is the third.
+
+## `tty7 wait`
+
+```bash
+tty7 wait [%PANE] [--until STATE,…] [--changed] [--timeout SECS] [--interval MS]
+```
+
+Blocks until the pane's agent reaches one of the states you named.
+
+| Flag | Default | |
+|---|---|---|
+| `--until` | `waiting,done,exit` | Which states end the wait: `idle`, `working`, `waiting`, `done`, `exit` |
+| `--changed` | off | Ignore the state the pane was *already* in — only wake on one it moved into after the wait began |
+| `--timeout` | none | Give up after this many seconds, exiting 124 |
+| `--interval` | 500 ms | How often to poll |
+
+Exit codes are made for scripts:
+
+| Code | Meaning |
+|---|---|
+| `0` | A state you asked for was reached |
+| `124` | Timed out — the `timeout(1)` convention, so "not yet" is distinguishable from "broken" |
+| `1` | The worker died first; the JSON says `"status": "exit"` |
+
+The reply carries the agent's own message and its native session id, so a
+wake-up is directly actionable.
+
+### Why `--changed` matters
+
+The status the server keeps is a **level, not an event**. `done` stands until
+the next turn begins; `waiting` stands until the agent moves again.
+
+So a `wait` issued immediately after a `send` can answer with the *previous*
+turn's state, before the worker has even read the input. `--changed` refuses the
+state the pane was already in, which is what every round after the first needs.
+Without it, the JSON's `stale` flag tells you whether that happened.
+
+## Watching everything at once
+
+```bash
+tty7 agents # every agent on the machine: pane, agent, status, message
+tty7 agents --json # the same, parseable
+```
+
+If you are an agent yourself, you are in that list too.
+
+## The Claude Code skill
+
+**Settings → Agents** has a switch that installs
+`~/.claude/skills/tty7-orchestration` — a skill teaching a *primary* Claude Code
+agent this whole delegation loop: open a worker pane, send it one bounded task,
+`wait` on it, answer what it asks, collect the result, close the pane.
+
+It is a skill rather than a global instruction on purpose. Only its one-line
+description rides in context until something reaches for it, and worker agents
+never inherit orchestration authority.
+
+Uninstalling removes the file tty7 wrote and refuses to touch one it did not, so
+a hand-written skill that happens to share the directory name survives.
+
+## Rules of the road
+
+
+ The panes on a machine are somebody's real work, and some of them are other
+ agents mid-task. Treat anything you did not create as read-only.
+
+
+- **Never `send` into a pane you did not open.** Check `tty7 agents` first.
+- **Never close a pane, tab, or workspace you did not create.**
+- **Never `server stop` or `server restart`.** Every pane on the machine dies
+ with it.
+- **Clean up what you did create** — `tty7 pane close %83` when you are done.
+
+The full agent-facing contract is in [the skill](/cli/agent-skill).
diff --git a/docs/agents/overview.mdx b/docs/agents/overview.mdx
new file mode 100644
index 00000000..903c94a6
--- /dev/null
+++ b/docs/agents/overview.mdx
@@ -0,0 +1,107 @@
+---
+title: "Coding agents"
+description: "What tty7 does around Claude Code, Codex, and 16 others — without ever wrapping them."
+---
+
+tty7 recognises coding agents running in a pane and builds around them. It does
+not wrap them, proxy them, or replace their interface: the agent you start is
+the agent you get, running in a normal PTY, with its own UI. tty7 adds the
+things a terminal is in a position to add — who is running where, what they
+need, and what changed.
+
+
+
+
+
+## Which agents
+
+Eighteen CLIs are recognised on sight, by the command running in the pane:
+
+| Agent | Command |
+|---|---|
+| Claude Code | `claude`, `claude-code` |
+| Codex | `codex`, `codex-cli` |
+| Gemini | `gemini`, `gemini-cli` |
+| Copilot | `copilot` |
+| Cursor | `cursor-agent` |
+| Amp | `amp` |
+| OpenCode | `opencode` |
+| Aider | `aider`, `aider-chat` |
+| Goose | `goose` |
+| Droid | `droid` |
+| Grok | `grok` |
+| Qwen Code | `qwen`, `qwen-code` |
+| Auggie | `auggie` |
+| Hermes | `hermes` |
+| Vibe | `vibe`, `vibe-acp` |
+| Antigravity | `agy`, `antigravity` |
+| Pi | `pi` |
+| Oh My Pi | `omp` |
+
+Detection sees through the usual disguises: a full path, a `.cmd` or `.exe` on
+Windows, leading environment assignments, and an interpreter in front
+(`node .../claude/cli.js`).
+
+### Your own wrapper
+
+If you launch agents through a wrapper script, map its name to an agent in
+`config.json`:
+
+```json
+{
+ "agent_commands": {
+ "cc": "claude",
+ "work": "codex"
+ }
+}
+```
+
+The key is your command's name; the value is one of the slugs above (`claude`,
+`codex`, `gemini`, `aider`, `amp`, `opencode`, `copilot`, `cursor`, `goose`,
+`droid`, `pi`, `auggie`, `hermes`, `vibe`, `antigravity`, `grok`, `qwen`,
+`omp`).
+
+## What you get for free
+
+Just by running an agent in a pane:
+
+
+
+ The tab chip and sidebar row show which agent runs where, so ten tabs stay
+ legible.
+
+
+ The branch and working-tree diff on the row, refreshed as the agent works.
+
+
+ A pane lost to a reboot relaunches the conversation, carrying its original
+ flags. [More →](/agents/sessions)
+
+
+ Palette commands that hand the current selection or the repo's `git diff` to
+ the running agent as a prompt.
+
+
+
+## What needs a hook
+
+Live status — **working**, **needs your input**, **done** — comes from the agent
+itself, over a channel tty7 installs into that agent's configuration. It powers
+the status dots, the notifications, the tray icon, and `tty7 wait`.
+
+Installing takes one click per agent under **Settings → Agents**.
+[Status and notifications →](/agents/status)
+
+## Where to go next
+
+
+
+ Hooks, status dots, the tray icon.
+
+
+ Resume, fork, and copying a session id.
+
+
+ One agent driving another with `tty7 wait`.
+
+
diff --git a/docs/agents/sessions.mdx b/docs/agents/sessions.mdx
new file mode 100644
index 00000000..7f70818f
--- /dev/null
+++ b/docs/agents/sessions.mdx
@@ -0,0 +1,60 @@
+---
+title: "Agent sessions"
+description: "Resuming a conversation after a reboot, forking a live one, and getting at the session id."
+---
+
+Coding agents keep their own conversation history, addressed by a session id.
+Because tty7's hooks learn that id, it can do three things with it.
+
+## Resume after a restart
+
+When the server goes away — a reboot, a crash, a deliberate restart — the shells
+go with it. Panes that were running an agent relaunch the conversation on
+restore instead of coming back to a bare prompt:
+
+```bash
+claude --dangerously-skip-permissions --resume 8f3c…
+```
+
+The original launch flags are replayed, so the pane comes back the way you
+started it, not the way the defaults would.
+
+Supported for Claude Code, Codex, Gemini, OpenCode, Amp, Cursor, Copilot, Grok,
+Pi, and Oh My Pi. Turn it off with `restore_agent_sessions: false`.
+
+
+ Resume needs the agent's hooks installed, since the session id comes from
+ them. [Installing hooks →](/agents/status)
+
+
+## Fork a live session
+
+Forking branches a running conversation into a second, independent one. The
+original keeps going untouched; both continue separately from the same history.
+
+Right-click a **pane** to fork into a split — the menu offers a placement —
+or right-click the **tab or sidebar row** to open the fork in a new tab.
+
+| Agent | What tty7 runs |
+|---|---|
+| Claude Code | `claude --resume --fork-session` |
+| Codex | `codex fork ` |
+| Grok | `grok --resume --fork-session` |
+| OpenCode | `opencode --session --fork` |
+| Oh My Pi | `omp --fork ` |
+
+It is the agent's own fork command, run in a new pane — nothing is copied by
+tty7 itself.
+
+
+ A fork duplicates the whole transcript in the agent's session store, so
+ forking repeatedly costs real disk. A pane on a
+ [remote machine](/remote/workspaces) cannot fork, because the command would
+ run against the local agent.
+
+
+## Copy the session id
+
+**Copy Session ID** — in the pane's right-click menu, beside *Copy Working
+Directory*, and in the command palette — puts the agent's native id on the
+clipboard. Paste it into `codex resume`, a bug report, or another tool.
diff --git a/docs/agents/status.mdx b/docs/agents/status.mdx
new file mode 100644
index 00000000..d9799e53
--- /dev/null
+++ b/docs/agents/status.mdx
@@ -0,0 +1,83 @@
+---
+title: "Status and notifications"
+description: "Installing the hooks, reading the dots, and being told when an agent needs you."
+---
+
+An agent working for two minutes and an agent that stopped ninety seconds ago
+waiting for permission look identical from outside. tty7 fixes that by letting
+the agent say which one it is.
+
+## Installing the hooks
+
+**Settings → Agents** lists every agent that can report status, with an
+**Install** button beside each:
+
+| Agent | |
+|---|---|
+| Claude Code · Codex · Copilot · OpenCode · Pi · Grok · Oh My Pi | Hooks available |
+| Gemini · Aider · Amp · Cursor · Goose · Droid · Auggie · Hermes · Vibe · Antigravity · Qwen Code | Detected and labelled, but no status channel yet |
+
+Installing writes into that agent's own configuration directory and can be
+undone from the same row — the button becomes **Uninstall**, and reads
+**Outdated** with an **Update** when tty7 ships a newer hook.
+
+
+ The hooks only do anything inside tty7. Running the same agent in another
+ terminal is unaffected.
+
+
+Connected [remote machines](/remote/workspaces) get their own row, so an agent
+running on a dev box reports status to the window you are watching it from.
+
+## The status dot
+
+Every tab chip and sidebar row carries a dot:
+
+| Dot | Meaning |
+|---|---|
+| 🔵 **Blue** | Working |
+| 🟠 **Amber** | Needs your input — a permission prompt, a question |
+| 🟢 **Green** | Done with this turn |
+
+
+
+
+
+The same three states are what `tty7 agents` reports as `running` / `waiting` /
+`idle`, and what [`tty7 wait`](/agents/orchestration) blocks on.
+
+## Notifications
+
+Two, both following your **Settings → Window & Tabs → Notifications** policy:
+
+- **"needs your permission…"** the moment an agent blocks on you
+- **"finished after 42s"** at the end of a turn
+
+Which means that by default — *When unfocused* — you are told the instant you
+are the bottleneck, and left alone while you are watching.
+
+## The tray icon
+
+tty7 keeps a status item in the system tray (menu bar on macOS). It flips to an
+attention state the moment *any* agent anywhere needs input, so you can see it
+without the window in front of you.
+
+Its menu lists every agent pane with its brand avatar and status dot — click one
+to reveal it — and also holds the notification policy switch and **Quit and Stop
+Server…**.
+
+Turn it off with **Settings → Window & Tabs → Show tray icon**
+(`show_tray_icon: false`).
+
+## Sending an agent some context
+
+Two command-palette entries hand what is in front of you to the agent running in
+the pane, as a ready-made prompt:
+
+| Command | Sends |
+|---|---|
+| **Agent: Send Selection** | The current terminal selection |
+| **Agent: Send Git Diff for Review** | The repository's `git diff` |
+
+If nothing recognisable is running, tty7 says *"No running coding agent found"*
+rather than typing into your shell.
diff --git a/docs/cli/agent-skill.mdx b/docs/cli/agent-skill.mdx
new file mode 100644
index 00000000..12697ffc
--- /dev/null
+++ b/docs/cli/agent-skill.mdx
@@ -0,0 +1,84 @@
+---
+title: "The agent skill"
+description: "Teaching a coding agent to use tty7 properly — including when not to."
+---
+
+The CLI is only half of the story. An agent has to know *when* reaching for a
+pane beats running a command, and — more importantly — which panes it must not
+touch. That is what the skill is for.
+
+## Installing it
+
+```bash
+npx skills add l0ng-ai/tty7
+```
+
+The source lives at
+[`skills/tty7/`](https://github.com/l0ng-ai/tty7/tree/main/skills/tty7) in the
+repository: a `SKILL.md` and a full command reference.
+
+
+ This is separate from the **orchestration** skill installed from **Settings →
+ Agents**, which teaches a *primary* agent to delegate to worker agents. This
+ one teaches any agent to drive tty7 at all.
+ [Orchestration →](/agents/orchestration)
+
+
+## What it teaches
+
+### When to use a pane instead of a plain command
+
+The Bash-style tool an agent already has is right for anything that starts, does
+its job, and exits. A pane is right when:
+
+- **It should not block.** A dev server, a watcher, `tail -f`, a long test run.
+- **It is interactive or stateful.** A REPL, `ssh`, a database shell — anything
+ where you send, read, then send again. A pane keeps the session alive between
+ turns; a one-shot call cannot.
+- **It needs a real TTY.** Programs that detect a pipe and change behaviour —
+ colour, progress bars, TUIs, `top`, raw mode.
+- **The user should be able to watch.** Anything in a pane shows up live in
+ their window. That is often the whole point.
+- **You are being asked about something you did not start.** "What's running in
+ that pane?", "why is port 3000 taken?", "what are my agents doing?"
+
+### The safety rules
+
+
+ The panes on this machine are the user's real work, and some of them are other
+ coding agents mid-task. Anything the agent did not create is read-only.
+
+
+- **Never `send` into a pane you did not open.** Keystrokes land in the middle
+ of whatever is happening there. Check `tty7 agents` first.
+- **Never close a pane, tab, or workspace you did not create.**
+- **Never `server stop` or `server restart`.** Every pane on the machine dies
+ with the server, including yours.
+- **Never `tty7 server start` on your own initiative** when `doctor` says the
+ server is unreachable — starting one the user did not ask for changes what
+ their GUI attaches to. Tell them instead.
+- **Clean up what you did create.** `tty7 pane close %83` when the scratch pane
+ is done with.
+
+### The reliable idioms
+
+Rather than screen-scraping, the skill points agents at the primitives that
+actually answer the question:
+
+```bash
+# is it finished? — when only the depth-0 shell is left, yes
+tty7 procs %83 --json
+
+# the answer, not the view
+tty7 send "$PANE" 'cargo test > /tmp/t.log 2>&1; echo $? > /tmp/t.rc' --enter
+
+# wake up exactly when the other agent needs something
+tty7 wait %3 --until waiting,done --changed --timeout 600
+```
+
+## For humans writing their own tooling
+
+The same material is worth reading even if you are not an agent — it is the
+shortest description of how to use tty7 as a job runner. Start with the
+[CLI overview](/cli/overview), then the
+[command reference](/cli/reference).
diff --git a/docs/cli/overview.mdx b/docs/cli/overview.mdx
new file mode 100644
index 00000000..ac1c7336
--- /dev/null
+++ b/docs/cli/overview.mdx
@@ -0,0 +1,147 @@
+---
+title: "The tty7 command"
+description: "Driving the workbench from a script, a Makefile, or another agent."
+---
+
+`tty7` is a thin, non-interactive client of the tty7 server. Every verb runs and
+exits; `--json` makes the output machine-readable. **The GUI does not have to be
+running** — the server is what owns the panes.
+
+It ships inside every installer and is put on PATH at launch, so it works from
+any terminal on the machine. [Installation →](/getting-started/installation#the-tty7-command)
+
+## Start with `doctor`
+
+```bash
+tty7 doctor
+```
+
+One table that answers everything you need before doing anything else: whether a
+server is reachable, whether its wire dialect matches this binary, and whether
+`TTY7_CONFIG_DIR` / `TTY7_WS` / `TTY7_PANE` are set — that is, whether you are
+running *inside* a tty7 pane.
+
+Being inside a pane matters because the address-taking verbs (`split`, `send`,
+`capture`, `procs`, `pane close`) default to `$TTY7_PANE`, and `run --keep`
+files its pane into `$TTY7_WS`. Outside one you must name a target, and the
+error says so rather than guessing.
+
+## Addresses
+
+| Shape | Means | Stable? |
+|---|---|---|
+| `%42` | A pane | **Yes** — a pane keeps its id for its whole life |
+| `@7` | A tab, numbered across the whole machine in tree order | **No** — it shifts whenever any workspace or tab appears or disappears |
+| `api` · `76698a44` · a full UUID | A workspace, by name, unique id prefix, or id | Yes |
+
+Re-resolve `@N` immediately before using it. Pane and workspace ids are safe to
+remember.
+
+## Two ways to run something
+
+### Blocking, with a real exit code
+
+```bash
+tty7 run -- cargo test # streams to stdout, exits with cargo's code
+tty7 run --cwd /path -- make
+tty7 run --keep -- cargo build # leaves the pane behind as a new tab
+```
+
+The closest thing to running the command yourself — the difference is that it
+gets a real PTY (so colour, progress bars, and TUIs behave), and that you can
+watch it happen in the window.
+
+
+ Everything after `--` belongs to the child: `tty7 run -- cargo test --keep`
+ passes `--keep` to cargo, not to tty7.
+
+
+### Non-blocking: a pane you talk to over time
+
+This is the one worth reaching for. Get a pane, give it work, come back.
+
+```bash
+PANE=$(tty7 split --v) # or --h; prints "%83"
+tty7 send "$PANE" 'npm run dev' --enter
+# ... later
+tty7 capture "$PANE" --plain
+tty7 pane close "$PANE"
+```
+
+If you are not inside a tty7 pane there is nothing to split, so make your own
+place to work:
+
+```bash
+tty7 new --json /path/to/repo # {"id": "...", "pane": 83}
+```
+
+## Reading a pane
+
+```bash
+tty7 capture %83 --plain
+```
+
+`capture` returns what the server stored. Without `--plain` that is the raw
+bytes, escapes and all. With `--plain` those bytes are replayed through a real
+terminal grid and you get the text that produced — which is not the same as
+stripping escapes yourself:
+
+- A line the shell wrapped at the pane width comes back as **one** line
+- A progress bar that rewrote itself with `\r` reads as its **final** value
+- Cursor addressing puts text **where the program put it**, so a TUI's screen
+ lands where it was drawn
+
+Use `--plain` whenever a human would want to read the output.
+
+
+ A screen is a rectangle. Whatever scrolled off the top is gone, and an exit
+ code was never on it. When you want the *answer* rather than the *view*, have
+ the shell write it somewhere clean:
+
+ ```bash
+ tty7 send "$PANE" 'cargo test > /tmp/t.log 2>&1; echo $? > /tmp/t.rc' --enter
+ ```
+
+
+## Knowing when something finished
+
+```bash
+tty7 procs %83
+```
+
+The process tree inside the pane, indented by depth, with `*` on the foreground
+process — plus the ports those processes are listening on. **When the only entry
+left is the depth-0 shell, the command is done.** That is far more reliable than
+grepping the screen for a sentinel that can wrap or echo twice.
+
+For agents specifically, use [`tty7 wait`](/agents/orchestration) instead of
+polling.
+
+## Looking around
+
+```bash
+tty7 ls # every workspace: tabs, panes, who's attached
+tty7 ws tree api # one workspace as a tree
+tty7 pane ls --all # every pane, including orphans no workspace holds
+tty7 agents # every coding agent and its status
+tty7 status # server pid, uptime, pane count, build, socket
+tty7 machine ls # this machine plus any linked remotes
+tty7 events # stream server events until interrupted
+```
+
+`--json` on any of them, `-q` to suppress success output (errors still print).
+
+## Remote machines
+
+```bash
+tty7 -m devbox ls
+tty7 -m devbox run -- cargo test
+```
+
+`-m` routes over a link the local server already holds. It will not dial a fresh
+connection — connect from the GUI first.
+[Remote workspaces →](/remote/workspaces)
+
+
+ Every verb, flag, and JSON shape.
+
diff --git a/docs/cli/reference.mdx b/docs/cli/reference.mdx
new file mode 100644
index 00000000..254abd98
--- /dev/null
+++ b/docs/cli/reference.mdx
@@ -0,0 +1,253 @@
+---
+title: "Command reference"
+description: "Every verb, its flags, and the JSON it emits under --json."
+---
+
+## Global flags
+
+Accepted anywhere on the line, before or after the subcommand.
+
+| Flag | Effect |
+|---|---|
+| `-m, --machine ` | Route to a linked machine over the local server's existing link. Matches the full link key (`me@devbox:22`) or the bare host (`devbox`). SSH links only; a down link, or a jump/proxy chain, is refused with a reason rather than dialled fresh. |
+| `--json` | One JSON object on stdout instead of the human table. |
+| `-q, --quiet` | No output on success. Errors still go to stderr. |
+
+## Environment
+
+Set inside every tty7 pane, inherited by anything launched from one.
+
+| Variable | Meaning |
+|---|---|
+| `TTY7_PANE` | This pane's id, e.g. `71` or `%71` (both accepted). Default target of `split`, `send`, `capture`, `procs`, `pane close`. |
+| `TTY7_WS` | This pane's workspace id. Default for `run --keep`, `tab new`, `ws tree`. |
+| `TTY7_CONFIG_DIR` | The server's config dir — how the CLI finds the right server. You never pass a socket path. |
+
+Outside a tty7 shell, address-taking verbs fail with
+`not inside a tty7 shell — pass an explicit %pane/@tab/workspace`.
+
+## Exit codes
+
+| Code | Meaning |
+|---|---|
+| `0` | Success |
+| `1` | The command failed; one line on stderr, prefixed `tty7:` |
+| `2` | Usage error — unknown verb, missing argument, bad type |
+| `124` | `tty7 wait` timed out (the `timeout(1)` convention) |
+| `141` | Unix only: the reader hung up — piping into `head -1`, say — and SIGPIPE ended it, exactly as it ends `cat`. Not a failure. Windows reports 0 for the same thing, having no signal to imitate. |
+| *other* | Only from `tty7 run`, which passes the child's exit code through |
+
+If `run` cannot learn the child's code it prints a note to stderr and exits 1
+with `"exit_code_known": false` in the JSON — that is how you tell a real 1 from
+a stand-in.
+
+## Top-level verbs
+
+### `tty7 ls`
+
+Same as `ws ls`. Table: `WORKSPACE NAME TABS PANES ATTACHED`.
+JSON: `{"workspaces":[{"id","name","tabs","panes","attached"}]}`.
+
+`ATTACHED` names the host holding the workspace — a GUI window, or another
+client — and is `-` when nobody is.
+
+### `tty7 run [--keep] [--cwd DIR] [--ws WORKSPACE] -- CMD...`
+
+Spawns a pane running `CMD`, streams its output to stdout, waits, and exits with
+its code. The command must come after `--`.
+
+- `--keep` leaves the pane alive as a new tab afterwards. Needs a workspace, so
+ it requires `--ws` or `$TTY7_WS` — without one it is an error, not a silent
+ fallback.
+- `--cwd` sets the working directory. `--ws` also sets the pane's `TTY7_WS`.
+- Interrupting `run` can leave the pane behind as an orphan — see
+ `pane ls --all`.
+
+JSON: `{"pane","exit","exit_code_known","kept"}`, printed **after** the streamed
+output. The combined stream is not valid JSON — read the last line.
+
+### `tty7 new [PATH] [--open]`
+
+Creates a workspace plus its first tab and shell, at `PATH` if given. Prints the
+workspace id. JSON: `{"id","pane","opened"}`.
+
+`--open` also puts a window on it, if a GUI is running on this machine. Without
+it the workspace still appears in the switcher; it just waits to be opened.
+
+### `tty7 split [%PANE] (--v|--h) [--ratio R]`
+
+Alias of `pane split`. Splits `%PANE` (default `$TTY7_PANE`), spawning a shell
+in the same cwd. Exactly one axis is required — `--v`/`--vertical` puts the new
+pane below, `--h`/`--horizontal` to the right. `--ratio` (default `0.5`) is the
+share kept by the *existing* pane. Prints `%NN`. JSON: `{"pane"}`.
+
+### `tty7 send [%PANE] TEXT [--enter]`
+
+Types `TEXT` into the pane as keystrokes; `--enter` appends CR. With one
+argument the text is the argument and the pane comes from `$TTY7_PANE` — but a
+lone `%42` is rejected as a missing-text error rather than typed.
+JSON: `{"pane","sent","enter"}`.
+
+### `tty7 capture [%PANE] [--plain] [--scrollback]`
+
+The pane's replay. Two independent choices:
+
+**How much** — the newest scrollback segment by default, the whole ring with
+`--scrollback`. The ring splits into segments on resize, so for a pane that was
+never resized the two are identical.
+
+**In what form** — without `--plain`, the stored bytes with ANSI escapes intact,
+decoded as UTF-8 (invalid bytes become U+FFFD). With `--plain`, those bytes
+replayed through a terminal grid and printed as the text they produced.
+
+Either way it is a snapshot, not a stream: it collects the replay, settles for
+~300 ms, and returns. Call it again for a newer one.
+JSON: `{"pane","text"}`.
+
+### `tty7 procs [%PANE]`
+
+The process tree inside the pane, indented by depth, `*` on the foreground
+process — then a second table of ports those processes are listening on. Prints
+`nothing running in this pane` when both are empty.
+
+JSON: `{"procs":[{"pid","name","depth","foreground"}],"ports":[{"port","pid","name"}]}`.
+
+### `tty7 agents`
+
+Every pane running a recognised coding agent. Table:
+`PANE AGENT STATUS MESSAGE`, status one of `running` / `waiting` / `idle`.
+JSON: `{"agents":[...]}`.
+
+### `tty7 wait [%PANE] [--until STATE,…] [--changed] [--timeout SECS] [--interval MS]`
+
+Blocks until the pane's agent reaches one of the named states.
+
+| Flag | Default | |
+|---|---|---|
+| `--until` | `waiting,done,exit` | `idle`, `working`, `waiting`, `done`, `exit` |
+| `--changed` | off | Only wake on a state the pane moved into *after* the wait began |
+| `--timeout` | none | Give up after N seconds, exiting `124` |
+| `--interval` | `500` | Poll interval in ms (50–3,600,000) |
+
+The reply carries the agent's message and native session id. The JSON's `stale`
+flag says whether the answer might belong to the previous turn.
+[Orchestration →](/agents/orchestration)
+
+### `tty7 events`
+
+Streams server events until interrupted, one per line — pane exits, agent status
+changes, workspace preemption, layout deltas. `--json` makes it NDJSON. Blocks
+forever; run it with a timeout or in the background.
+
+### `tty7 status`
+
+Same as `server status`: pid, uptime, pane count, dialect versions, build,
+socket path. JSON is the `ServerStatus` object itself (`pid`, `uptime_secs`,
+`panes`, `control_version`, `protocol_version`, `build`, `socket`).
+
+### `tty7 doctor`
+
+The install check: the three environment variables, whether the server answers,
+whether its control and protocol versions match this binary, pid/uptime/panes,
+and how many machine links exist. Adds a note when you are not inside a tty7
+shell.
+
+JSON: `{"context":{"config_dir","workspace","pane"},"server":{"reachable","dialect_ok","build","status","routes"}}`
+— the context fields are booleans, not values.
+
+## `ws` — workspaces
+
+Address a workspace by name, by full id, or by a unique id prefix (the 8-char
+prefix `tty7 ls` prints). An ambiguous name or prefix is an error that lists the
+candidates.
+
+| Command | Effect | JSON |
+|---|---|---|
+| `ws ls` | Every workspace | `{"workspaces":[...]}` |
+| `ws tree [WORKSPACE]` | One workspace as a tree: tabs, split axes and ratios, panes with cwds | The whole workspace object: `{"id","name","last_active","tabs":[{"id","name","sidebar_group","root",…}]}` |
+| `ws new [NAME]` | An empty workspace (no tab, no pane) | `{"id","name"}` |
+| `ws rename WORKSPACE NAME` | Name or rename | `{"id","name"}` |
+| `ws rm WORKSPACE` | Delete the workspace | `{"removed"}` |
+| `ws attach WORKSPACE` | Become its controlling client | `{"attached","took_over_from"}` |
+| `ws detach WORKSPACE` | Let go without interrupting anything | `{"detached"}` |
+
+
+ `ws rm` does **not** kill the panes it held — they keep running as orphans
+ with no workspace. Find them with `pane ls --all` and close them one by one.
+
+
+Prefer `tty7 new ` over `ws new` when you want something usable: `ws new`
+leaves an empty workspace you then have to populate, while `tty7 new --json`
+hands back both ids at once.
+
+The `root` node in `ws tree --json` is externally tagged, so a leaf is
+`{"Leaf":{"pane":31}}` and a split is `{"Split":{"axis","ratio","a","b"}}` with
+`a`/`b` nested the same way.
+
+## `tab` — tabs
+
+`@N` numbers tabs across the **whole machine** in tree order, densely from `@1`.
+The numbering shifts whenever any workspace or tab is created or removed, so
+resolve it immediately before use. A full tab UUID also works: `@`.
+
+| Command | Effect | JSON |
+|---|---|---|
+| `tab ls [WORKSPACE]` | Tabs of a workspace | `{"workspace","tabs":[{"ordinal","id","name","label","agent","group","panes":[…]}]}` |
+| `tab new [WORKSPACE] [--cwd DIR]` | Add a tab with a fresh shell | `{"tab","pane"}` |
+| `tab close @TAB` | Close the tab and every pane in it | `{"closed"}` |
+| `tab rename @TAB NAME` | Name or rename | `{"tab","name"}` |
+| `tab move @TAB INDEX` | Reposition within its workspace | `{"tab","to"}` |
+
+`GROUP` is the heading the GUI's sidebar files the tab under, shown by its last
+segment. Read-only from here: with the default repo grouping the GUI recomputes
+it from the tab's working directory.
+
+`label` falls back through the best evidence available — the name if someone set
+one, else the agent running there, else the last segment of the cwd, else the
+foreground process. `name` stays literal, so a script can tell a real name from
+a stand-in.
+
+## `pane` — panes
+
+| Command | Effect | JSON |
+|---|---|---|
+| `pane ls [WORKSPACE]` | Panes with their workspace, tab, cwd, live flag | `{"panes":[…]}` |
+| `pane ls --all` | The server's whole pane registry, including orphans | `{"panes":[…],"orphans":N}` |
+| `pane split …` | Identical to top-level `split` | `{"pane"}` |
+| `pane close [%PANE]` | Close the pane; its shell is hung up | `{"closed"}` |
+
+`--all` is the one that shows leaks. Each entry is
+`{"pane","workspace","orphan","owner","title","cwd","live"}`: `owner` is
+`tty7-cli` for panes this CLI spawned, and `orphan: true` means no workspace
+holds it. An interrupted `run` and a removed workspace both leave orphans here.
+
+`title` is usually the running command — `claude`, `nvim`, `cargo` — which makes
+`pane ls --all --json` a quick way to find "the pane running X".
+
+## `machine` — remotes
+
+`machine ls` lists the local machine plus every link the server holds:
+`MACHINE KIND CONNECTED`. JSON: `{"machines":[{"key","kind","connected"}]}`.
+
+## `server` — the daemon
+
+| Command | Effect |
+|---|---|
+| `server status` | Same as `tty7 status` |
+| `server logs` | Tail the server log; prints the path, and says so when logging was never enabled (`TTY7_LOG=info` before the server starts) |
+| `server start` | Bring up a server on this machine |
+| `server stop` | Stop it — **every pane on the machine dies** |
+| `server restart` | Stop, then start — same consequence |
+
+
+ Do not run `start`, `stop`, or `restart` on someone else's behalf. They change
+ or destroy what the user's GUI is attached to.
+
+
+## Not implemented yet
+
+These parse and then exit 1 with an explanation:
+
+- `ws stop` — the control dialect has no workspace-stop request yet
+- `machine connect` / `machine disconnect` — use the GUI's connection manager
+- bare `tty7 ` (launch or focus the GUI)
diff --git a/docs/customization/fonts.mdx b/docs/customization/fonts.mdx
new file mode 100644
index 00000000..544fc5ba
--- /dev/null
+++ b/docs/customization/fonts.mdx
@@ -0,0 +1,75 @@
+---
+title: "Fonts"
+description: "The bundled default, fallback chains, ligatures, and why CJK needs a word."
+---
+
+**Settings → Appearance → Typography** covers the everyday choices; the rest is
+`config.json`.
+
+| Setting | Default | |
+|---|---|---|
+| **Font family** | Hack | Picked from fonts installed on your system |
+| **Font size** | 15 px | The terminal grid |
+| **Interface font size** | 16 px | Everything outside the grid (12–24) |
+| **Line height** | 1.4 | A multiple of the font size |
+| **Bold font** / **Italic font** | — | Distinct faces, when you want them |
+| **Font ligatures** | off | Contextual alternates stay off unless you ask |
+
+## Hack is bundled
+
+The default font ships inside the binary. It renders identically on every
+machine without relying on a system install, so a fresh laptop looks like the
+one you set up last year.
+
+## Fallbacks
+
+`font_family` is the primary face; `font_fallbacks` is an ordered list tried in
+turn for anything the primary lacks.
+
+```json
+{
+ "font_family": "JetBrains Mono",
+ "font_fallbacks": ["Maple Mono NF CN", "PingFang SC", "Apple Color Emoji"]
+}
+```
+
+The defaults name faces the host OS actually ships — PingFang SC and Apple Color
+Emoji on macOS, Microsoft YaHei and Segoe UI Emoji on Windows, Noto on Linux.
+Those stock names are appended to whatever list you write, too, so a
+`config.json` copied from another platform still resolves.
+
+## OpenType features
+
+`font_features` passes tags straight through to the shaper:
+
+```json
+{
+ "font_features": { "calt": true, "liga": 1, "ss01": true, "zero": false }
+}
+```
+
+A tag must be four alphanumeric characters; `true`/`false` map to `1`/`0`.
+Anything malformed is skipped with a log line rather than failing the whole
+config.
+
+## CJK and the two-column grid
+
+
+ A cell is one advance of the primary face, and a wide (CJK) character is
+ pinned to exactly two of them. A CJK fallback sits flush in its slot only if
+ its ideographs advance **twice** the primary's Latin advance.
+
+
+Bundled Hack advances 0.60205em, so a two-column slot is 1.2041em — while every
+stock CJK face (Microsoft YaHei, PingFang SC, Noto Sans CJK) advances 1.0em.
+Those glyphs get left-aligned in the slot, leaving a ~0.2em gap on the right of
+every character.
+
+[Maple Mono NF CN](https://github.com/subframe7536/maple-font) is tried first on
+every platform for exactly this reason: 0.6em Latin, 1.2em CJK, an exact
+two-cell fit against Hack. It is referenced by name only, never bundled (~20 MB
+per weight) — install it and tty7 picks it up with no config change.
+
+If you want CJK set *tight* rather than merely even, change the **primary** face
+instead. One that advances 0.5em — Sarasa Mono SC, say — makes two columns
+exactly 1.0em.
diff --git a/docs/customization/keybindings.mdx b/docs/customization/keybindings.mdx
new file mode 100644
index 00000000..84386b0c
--- /dev/null
+++ b/docs/customization/keybindings.mdx
@@ -0,0 +1,94 @@
+---
+title: "Keybindings"
+description: "Rebinding anything, chord sequences, and the tmux preset."
+---
+
+**Settings → Keybindings** (⌘ ,) lists every shortcut in the app,
+grouped the same way the command palette is.
+
+## Rebinding
+
+Click a shortcut and press the new keys. It saves after a brief pause.
+
+| | |
+|---|---|
+| Press keys | Set the binding |
+| Press more keys | Chain a sequence — ⌃ B then X |
+| Esc | Cancel |
+| ⌫ | Remove the last key — or, pressed first, reset the shortcut to its default |
+
+**Restore all defaults** at the bottom undoes every rebinding at once. There is
+no undo for that one.
+
+
+
+
+
+## Actions with no default key
+
+Some actions ship deliberately unbound, because there is no obvious key left to
+take: pane resize and swap, workspace selection, most git commands, SFTP, and
+the panel tabs. They are all in the command palette, and all bindable here.
+
+## Editing `config.json` instead
+
+```json
+{
+ "keybindings": {
+ "SplitRight": "cmd-d",
+ "ResizePaneLeft": "ctrl-alt-left",
+ "ToggleSftp": "cmd-shift-u"
+ }
+}
+```
+
+The syntax is modifiers joined by `-`, then the key. Chords are separated by a
+space.
+
+| Token | Means |
+|---|---|
+| `secondary` | ⌘ on macOS, Ctrl elsewhere |
+| `cmd` · `ctrl` · `alt` · `shift` | Literal modifiers |
+| `ctrl-b n` | A two-key sequence |
+
+An unknown action name or an invalid keystroke is skipped with a warning in the
+log rather than breaking the rest of your bindings.
+
+The full action list is on the [keyboard shortcuts](/reference/keyboard-shortcuts)
+page.
+
+## The tmux preset
+
+**Settings → Keybindings → Preset → tmux** remaps pane and tab actions onto a
+prefix — ⌃ B by default, changeable in the **Prefix** field beside
+it.
+
+| | |
+|---|---|
+| ⌃ BC · X | New tab · close tab |
+| ⌃ B% · " | Split right · split down |
+| ⌃ B←→↑↓ | Move focus |
+| ⌃ B⌃ ←→↑↓ | Resize the pane |
+| ⌃ BO · ; | Next pane · previous pane |
+| ⌃ B{ · } | Swap with the previous · next pane |
+| ⌃ BZ | Zoom the pane |
+| ⌃ BN · P | Next tab · previous tab |
+| ⌃ B1…9 | Jump to a tab |
+
+Two details that make it livable:
+
+- A **bare prefix** reaches the shell after about a second, so ⌃ B
+ still works as "back one character" when you meant it.
+- **Prefix plus an unbound key** is passed straight through to the terminal, so
+ a tmux binding you did not remap still lands in whatever is running.
+
+## Some non-obvious defaults
+
+| | |
+|---|---|
+| ⇧ ⏎ · ⌥ ⏎ | Insert a newline at the prompt instead of submitting (`InsertNewline`) |
+| ⌘ ⇧ ⏎ | Zoom the focused pane |
+| ⌘ ⇧ E | Toggle the code panel |
+| ⌘ ⇧ R | Restart the SSH session in this pane |
+| ⌘ ⇧ O | Workspace switcher |
+| ⌘ ⇧ N | New workspace |
diff --git a/docs/customization/settings.mdx b/docs/customization/settings.mdx
new file mode 100644
index 00000000..a260f6eb
--- /dev/null
+++ b/docs/customization/settings.mdx
@@ -0,0 +1,86 @@
+---
+title: "Settings"
+description: "What lives in each section, and how the settings file works."
+---
+
+⌘ , opens Settings. There is a search box at the top that matches
+setting names *and* keywords, which is usually faster than remembering which
+section something is in.
+
+
+
+
+
+## The eight sections
+
+
+
+ Theme, sync with system, typography, cursor, transparency, language.
+
+
+ Shell and start directory, scrollback and scrolling, mouse, bell, per-pane
+ history.
+
+
+ Prompt features, selection & clipboard, keyboard (Option as Meta), links.
+
+
+ Hosts, defaults, security, and every per-profile field.
+
+
+ Hook installation per agent and per machine, the orchestration skill, the
+ CLI on PATH.
+
+
+ Startup window, tab bar position and grouping, notifications, tray icon.
+
+
+ Every shortcut, the tmux preset, the prefix.
+
+
+ Version, update channel, and the updater.
+
+
+
+## The settings file
+
+Everything the Settings window writes goes to one file:
+
+| | |
+|---|---|
+| macOS / Linux | `~/.config/tty7/config.json` |
+| Windows | `%APPDATA%\tty7\config.json` |
+
+Set `TTY7_CONFIG_DIR` to point the whole directory — config, themes, state —
+somewhere else.
+
+You can edit the file by hand; a handful of options exist only there. See the
+[configuration reference](/reference/configuration) for every key, its type, and
+its default.
+
+### How it handles mistakes
+
+The file is written atomically, and read forgivingly:
+
+- **A missing key** means the default — you only have to write what you change.
+- **An out-of-range number** is clamped into its band, not rejected.
+- **An unrecognised enum value** falls back to the default with a log line,
+ rather than failing the whole file.
+- **An unparseable file** is not overwritten. tty7 starts on defaults, keeps a
+ copy at `config.json.corrupt`, and says so in the log.
+
+
+ A UTF-8 BOM at the start of the file is tolerated, which matters if you edited
+ it in a Windows editor.
+
+
+## Language
+
+**Settings → Appearance → Language** switches the interface between English,
+简体中文, and 日本語. The choice is explicit — the system language is never
+inferred — and CLI output stays English regardless, so agent and script
+integrations keep a stable surface.
+
+```json
+{ "gui_language": "zh-CN" }
+```
diff --git a/docs/customization/themes.mdx b/docs/customization/themes.mdx
new file mode 100644
index 00000000..06774279
--- /dev/null
+++ b/docs/customization/themes.mdx
@@ -0,0 +1,113 @@
+---
+title: "Themes"
+description: "Nine built-ins, your own YAML themes, iTerm2 imports, and a colour editor."
+---
+
+**Settings → Appearance → Theme** — or **Change Theme…** in the command
+palette — opens the theme picker.
+
+## Built in
+
+| Light | Dark |
+|---|---|
+| Light *(default)* · One Light · Catppuccin Latte · Rosé Pine Dawn | Dark · Dracula · Harbor · One Dark Pro · Rosé Pine |
+
+
+
+
+
+## Following the system
+
+Turn on **Sync with system** and pick a theme for each appearance. tty7 follows
+the OS live — no restart, no reload.
+
+```json
+{
+ "theme_follow_system": true,
+ "theme_preset_light": "one_light",
+ "theme_preset_dark": "dracula"
+}
+```
+
+## Legible bright colours
+
+Some palettes put a bright ANSI colour so close to their own background that it
+disappears. **Legible bright colors** (on by default) brightens or darkens those
+just enough to be readable. Turn it off with `theme_legible_palette: false` if
+you want the palette exactly as authored.
+
+## Transparency
+
+**Settings → Appearance → Transparency**:
+
+| | |
+|---|---|
+| **Opacity** | 0.2–1.0, applied to every theme. *Follow theme* hands the decision back to the theme's own `opacity`. |
+| **Blur** | Blurs whatever is behind a translucent window (macOS). |
+| **Background material** | Windows only: *Auto*, *Blur*, *Mica*, *Mica Alt*, *Acrylic*, *Off*. Only the presets your Windows build supports are listed. |
+
+## Writing your own
+
+**Open themes folder** in Settings takes you to:
+
+| | |
+|---|---|
+| macOS / Linux | `~/.config/tty7/themes/` |
+| Windows | `%APPDATA%\tty7\themes\` |
+
+Drop a `.yaml` file in and it appears in the picker. The file name is the
+theme's id; `name` is what is shown.
+
+```yaml
+name: "Midnight"
+background: "#0d1117"
+foreground: "#c9d1d9"
+accent: "#3fdd8c"
+cursor: "#3fdd8c"
+selection: "#264f78"
+opacity: 0.95
+blur: true
+ansi:
+ normal: ["#484f58", "#ff7b72", "#3fb950", "#d29922", "#58a6ff", "#bc8cff", "#39c5cf", "#b1bac4"]
+ bright: ["#6e7681", "#ffa198", "#56d364", "#e3b341", "#79c0ff", "#d2a8ff", "#56d4dd", "#f0f6fc"]
+```
+
+Everything except `background`, `foreground`, `accent`, and `ansi` is optional.
+
+### Gradients and images
+
+`background` also takes two colours:
+
+```yaml
+background: { top: "#0d1117", bottom: "#161b22" }
+# or
+background: { left: "#0d1117", right: "#161b22" }
+```
+
+And a theme can carry an image behind the terminal:
+
+```yaml
+background_image:
+ path: "/Users/me/Pictures/wall.jpg"
+ opacity: 0.25
+```
+
+The Settings panel has a picker for both, so you rarely have to write this by
+hand.
+
+## Editing in the app
+
+Select a built-in theme and hit **Duplicate to edit** — built-ins are read-only,
+so the editor works on your copy. From there you get every colour, including the
+sixteen ANSI slots, plus the background image controls. Changes are written back
+to your themes folder as YAML.
+
+## Importing from iTerm2
+
+Drop an `.itermcolors` file into the themes folder and tty7 reads it directly —
+no conversion step.
+
+
+ A theme file tty7 could not load is listed in Settings under **Not loaded from
+ the themes folder**, with the reason, rather than silently ignored.
+
diff --git a/docs/docs.json b/docs/docs.json
new file mode 100644
index 00000000..762af9e8
--- /dev/null
+++ b/docs/docs.json
@@ -0,0 +1,142 @@
+{
+ "$schema": "https://mintlify.com/docs.json",
+ "theme": "mint",
+ "name": "tty7",
+ "description": "A terminal workbench: persistent sessions, remote work, agents.",
+ "colors": {
+ "primary": "#0FA968",
+ "light": "#3FDD8C",
+ "dark": "#0FA968"
+ },
+ "favicon": "/favicon.ico",
+ "logo": {
+ "light": "/logo/logo.svg",
+ "dark": "/logo/logo.svg",
+ "href": "https://github.com/l0ng-ai/tty7"
+ },
+ "navigation": {
+ "tabs": [
+ {
+ "tab": "Documentation",
+ "groups": [
+ {
+ "group": "Getting started",
+ "pages": [
+ "index",
+ "getting-started/installation",
+ "getting-started/first-launch",
+ "getting-started/concepts"
+ ]
+ },
+ {
+ "group": "The window",
+ "pages": [
+ "window/tabs-and-splits",
+ "window/sidebar",
+ "window/command-palette",
+ "window/search",
+ "window/side-panel"
+ ]
+ },
+ {
+ "group": "The terminal",
+ "pages": [
+ "terminal/prompt",
+ "terminal/history",
+ "terminal/selection-and-clipboard",
+ "terminal/links",
+ "terminal/mouse-and-scrolling"
+ ]
+ },
+ {
+ "group": "Coding agents",
+ "pages": [
+ "agents/overview",
+ "agents/status",
+ "agents/sessions",
+ "agents/orchestration"
+ ]
+ },
+ {
+ "group": "Remote work",
+ "pages": [
+ "remote/ssh",
+ "remote/sftp",
+ "remote/port-forwarding",
+ "remote/workspaces"
+ ]
+ },
+ {
+ "group": "Git",
+ "pages": [
+ "git/source-control",
+ "git/diffs",
+ "git/worktrees"
+ ]
+ },
+ {
+ "group": "Customization",
+ "pages": [
+ "customization/settings",
+ "customization/themes",
+ "customization/fonts",
+ "customization/keybindings"
+ ]
+ }
+ ]
+ },
+ {
+ "tab": "CLI",
+ "groups": [
+ {
+ "group": "The tty7 command",
+ "pages": [
+ "cli/overview",
+ "cli/reference",
+ "cli/agent-skill"
+ ]
+ }
+ ]
+ },
+ {
+ "tab": "Reference",
+ "groups": [
+ {
+ "group": "Reference",
+ "pages": [
+ "reference/configuration",
+ "reference/keyboard-shortcuts",
+ "reference/shell-integration",
+ "reference/updates",
+ "reference/privacy",
+ "reference/troubleshooting"
+ ]
+ }
+ ]
+ }
+ ]
+ },
+ "navbar": {
+ "links": [
+ {
+ "label": "GitHub",
+ "href": "https://github.com/l0ng-ai/tty7"
+ },
+ {
+ "label": "Discord",
+ "href": "https://discord.gg/s3dethqz2V"
+ }
+ ],
+ "primary": {
+ "type": "button",
+ "label": "Download",
+ "href": "https://github.com/l0ng-ai/tty7/releases"
+ }
+ },
+ "footer": {
+ "socials": {
+ "github": "https://github.com/l0ng-ai/tty7",
+ "discord": "https://discord.gg/s3dethqz2V"
+ }
+ }
+}
diff --git a/docs/favicon.ico b/docs/favicon.ico
new file mode 100644
index 00000000..f4cbfc6d
Binary files /dev/null and b/docs/favicon.ico differ
diff --git a/docs/features.md b/docs/features.md
deleted file mode 100644
index 7310b805..00000000
--- a/docs/features.md
+++ /dev/null
@@ -1,158 +0,0 @@
-# Features
-
-English · [简体中文](features.zh-CN.md)
-
-## Input
-
-- **Ghost suggestions** — your history completes the whole line as you type; → to accept
-- **Explained tab completion** — every flag and subcommand with its description, for ~100 common commands; when tty7 has nothing to offer the Tab falls through to your shell's own completion, and the whole feature can be turned off (Settings → Input → Prompt, or `tab_completion` in `config.json`)
-- **Syntax highlighting** — as you type, nothing to install
-- **Fuzzy history search** — ⌃ R shows what you ran, where, and whether it failed; turn it off (Settings → Input → Prompt, or `history_search` in `config.json`) and ⌃ R goes to your shell instead, so an fzf / percol binding keeps working
-- **History from day one** — your existing shell history works as-is and carries across sessions
-- **Line editing** — click to place the caret, mouse selection, word motion, undo
-- **Multi-line editing** — wrapped and multi-line commands edit in place; the grid shifts to keep the caret visible. ⇧ ⏎ · ⌥ ⏎ insert a newline instead of submitting (rebindable as `InsertNewline`); a plain ⏎ submits the whole buffer
-
-## In the window
-
-- **Tabs & splits** — always open in the current directory
-- **Rearrange splits by dragging** — hover a pane and a small grip appears along its top edge; drag it over the layout to put the pane somewhere else in the tab. Dropping on a pane's side goes in beside it — taking an equal share of the row or column it joins, or splitting that pane in half when the side faces across the layout rather than along it — dropping on its middle trades the two panes' places, and carrying it past a pane's outer side — the one facing the window rather than another pane — makes it a full-width or full-height band beside everything else, sized to an even share of what that side already holds — so a pane in the middle of a 2×2 becomes a full-height third column in one drag. The landing lights up while you drag, and only ever lights up when the drop would really change the layout
-- **Repo-grouped sidebar** — the left tab sidebar groups rows under a header per git repository, non-repo tabs in a trailing *Scratch* section; branch switches and in-repo `cd`s never move a row (`sidebar_grouping` in `config.json`: `repo` default, `none` for a flat list)
-- **Command palette** ⌘ P · scrollback search ⌘ F
-- **⌘/Ctrl-click links** (⌘ on macOS, Ctrl on Windows/Linux) · desktop notifications · copy on select (opt-in, Settings → Input → Selection & clipboard)
-- **Smart double-click selection** — double-click grabs the whole URL, file path, bracket/quote pair, or dictionary-segmented CJK word under the cursor; Shift-click extends a selection (toggle in Settings → Input → Selection & clipboard; word separators via `word_separators` in `config.json`)
-- **Nine themes, plus your own** — YAML seed themes with solid, gradient, or image backgrounds; iTerm2 `.itermcolors` import; in-app color editor with a background-image picker
-- **Sync with system** — Settings → Appearance; pick separate light and dark themes and tty7 follows the OS appearance live (`theme_follow_system`, `theme_preset_light` / `theme_preset_dark` in `config.json`)
-- **Window opacity & blur** — Settings → Appearance → Transparency; applies to every theme, *Follow theme* returns to the theme's own `opacity` / `blur`
-- **CJK / IME input**
-- **Windows Explorer menu** — the installer offers *Add “Open in tty7” to the folder context menu* as a setup task, off by default, and the uninstaller always takes it back out. Writing shell verbs is an install-time decision, so there is no runtime setting; a portable-zip install can do it itself with `tty7-app.exe --register-explorer-menu` (or `--unregister-explorer-menu`). Either way the keys land under `HKCU`, so only your own Windows account is affected
-
-## Fonts
-
-- **Hack is bundled** — it ships inside the binary, so the default renders identically everywhere without relying on a system install
-- **Primary + ordered fallbacks** — `font_family` and `font_fallbacks` in `config.json`; optional `font_family_bold` / `font_family_italic` for distinct faces, and `font_features` to pass OpenType features through (contextual ligatures stay off unless you ask for them)
-- **Platform-aware defaults** — the fallback list names faces the host OS actually ships (PingFang SC / Apple Color Emoji on macOS, Microsoft YaHei / Segoe UI Emoji on Windows, Noto on Linux). Those stock names are appended to a hand-written list too, so a `config.json` written on another platform still resolves
-
-### CJK and the two-column grid
-
-A cell is one advance of the primary face, and a wide (CJK) character is pinned
-to exactly two of them. A CJK fallback therefore sits flush in its slot only if
-its ideographs advance **twice** the primary's Latin advance.
-
-Bundled Hack advances 0.60205em, so a two-column slot is 1.2041em — while every
-stock CJK face (Microsoft YaHei, PingFang SC, Noto Sans CJK) advances 1.0em.
-Those glyphs get left-aligned in the slot and the leftover ~0.2em lands as a gap
-on the right of every character.
-
-[Maple Mono NF CN](https://github.com/subframe7536/maple-font) is tried first on
-every platform for exactly this reason — 0.6em Latin, 1.2em CJK, an exact
-two-cell fit against Hack. It is referenced by name only, never bundled (~20MB
-per weight): install it and tty7 picks it up with no config change.
-
-For CJK set *tight* rather than merely even, change the primary face instead —
-one that advances 0.5em (Sarasa Mono SC, say) makes two columns exactly 1.0em.
-
-## Coding agents
-
-tty7 recognizes third-party coding agents running in a pane (Claude Code,
-Codex, Gemini CLI, Aider, Amp, OpenCode, and 12 more) and adds around them —
-it never wraps or replaces the agent.
-
-- **Brand avatars** — the tab chip / sidebar row shows which agent runs where; custom wrappers map in via `agent_commands` in `config.json`
-- **Status dot** — working (blue) / needs your input (amber) / done (green), driven by agent-reported events over an OSC channel; Settings → Agents installs the hooks that feed it (Claude Code, Codex, Copilot CLI, OpenCode, Pi, Grok Build, Oh My Pi)
-- **Notifications** — "needs your permission…" the moment an agent blocks on you, and "finished after Ns" per turn, honoring your notification policy
-- **Branch at a glance** — each sidebar row shows its pane's git branch and working-tree diff (`+N −M`), refreshed on `cd` and when a command finishes; clicking the counts opens the diff overlay, and turning that off (Settings → Window & Tabs, or `sidebar_diff_preview: false` in `config.json`) keeps the readout while making it non-clickable
-- **Session resume** — panes lost to a reboot re-launch their agent conversation on restore, carrying the original launch flags (`claude --dangerously-skip-permissions --resume …`) (`restore_agent_sessions`, on by default)
-- **Fork session** — branch a live agent conversation into a second, independent one by shelling the agent's own fork command (`codex fork `, `claude --resume --fork-session`, also OpenCode, Grok Build, and Oh My Pi); the original is untouched and both continue separately. Right-click a pane to pick a split placement, or right-click the tab / sidebar row to open the fork in a new tab. Needs the agent's hooks installed, since the fork targets the session id they report; a remote pane can't fork, because the command would run against the local agent — and note a fork copies the whole transcript, so repeated forking costs real disk in the agent's own session store
-- **Copy Session ID** — put the agent's native session id on the clipboard, beside *Copy Working Directory*, for pasting into `codex resume`, a bug report, or another tool
-- **Context feed** — palette commands send the current selection or the repo's `git diff` to the running agent as a ready-made prompt
-- **Tray icon** — a system tray / menu bar item that flips to an attention state the moment any agent needs your input; its menu lists every agent pane (brand avatar + status dot, click to reveal), switches the notification policy, and offers *Quit and Stop Server…* alongside the plain session-keeping quit (`show_tray_icon`, on by default)
-- **`tty7 wait`** — the CLI's orchestration primitive: block until a pane's agent needs input or finishes its turn (`tty7 wait %3 --until waiting,done --changed --timeout 600`, exit 124 on timeout), so one agent can sleep until its peer blocks on a permission prompt instead of screen-scraping — then `tty7 capture %3 --plain` to read the result. The agent status is a level, not an event, so `--changed` ignores the state the pane was already in when the wait began; without it, the JSON's `stale` flag says whether the answer might belong to the previous turn
-- **`tty7` on PATH** — the CLI ships inside every installer and is put on PATH at launch, so a script or a coding agent can drive tty7 from any terminal. Inside a tty7 pane it works regardless, since panes inherit the app's environment. On Unix it is a symlink into whichever of `/opt/homebrew/bin`, `/usr/local/bin`, `~/.local/bin`, `~/bin`, `~/.cargo/bin` your PATH already covers; on Windows the install directory is appended to your user PATH, and the uninstaller takes it back out. A `tty7` you installed yourself is left alone, never replaced. Off via Settings → Agents or `install_cli_on_path: false` in `config.json`
-
-## SSH
-
-A native Rust SSH stack (russh) is the **only** path — profiles, credentials,
-and SFTP without shelling out to `ssh`. There is no system-ssh compat mode.
-
-- **QuickConnect** — type `user@host[:port]` in the palette and connect; IPv6 `[::1]:port` supported
-- **Saved profiles** — full connection config with passwords / passphrases in the OS keychain, never on disk
-- **`~/.ssh/config` aliases** — type one to connect (resolved natively — common fields, best-effort — over russh), or import them as profiles in Settings
-- **GUI auth** — in-pane sheets for password, key passphrase, 2FA, and host-key confirmation (new vs. changed)
-- **Built-in SFTP** — a slide-in file panel: browse, upload / download, rename / delete / chmod, drag to Finder
-- **Port forwarding** — Local / Remote / Dynamic, preconfigured or added live, plus ⌘/Ctrl-click `localhost:PORT` to auto-forward
-- **Jump hosts & proxies** — multi-hop via profile references or `ProxyJump`, ProxyCommand, SOCKS5 / HTTP
-
-| Entry point | Connects via |
-|---|---|
-| Saved profiles · QuickConnect · typed `user@host[:port]` | Native russh — SFTP · keychain · GUI auth · L/R/D forwards |
-| `~/.ssh/config` aliases | Resolved natively, then russh (`Match`/canonicalize/GSSAPI unsupported — no fallback) |
-
-## Keybindings
-
-Keys are shown in macOS notation — on Windows and Linux, read ⌘ as
-Ctrl. The essentials:
-
-| | |
-|---|---|
-| ⌘ T · ⌘ W · ⌘ ⇧ T | new tab · close tab · reopen closed tab |
-| ⌘ 1…⌘ 9 | jump to tab 1–9 |
-| ⌃ ⇥ · ⌃ ⇧ ⇥ | hold to walk the switcher forwards · backwards; it commits when you let go |
-| ⌘ D · ⌘ ⇧ D | split right · split down |
-| ⌘ ] · ⌘ [ | next pane · previous pane |
-| ⌘ ⌥ ←→↑↓ | focus the pane in that direction |
-| ⌘ ⏎ · ⌘ ⇧ ⏎ | toggle fullscreen · zoom pane |
-| ⌘ K | clear scrollback |
-| ⌘ P | command palette |
-| ⌘ F | search the scrollback |
-| ⌃ R | fuzzy-search shell history |
-| ⌘ + · ⌘ − · ⌘ 0 | font size up · down · reset |
-| ⌘ + wheel | zoom the font by scrolling over a terminal |
-
-**Settings → Keybindings** (⌘ ,) lists every shortcut. Click one,
-press the new keys (Esc cancels, Backspace resets to
-default), and it takes effect immediately. Pane resize and swap have no default
-keys — bind them here or run them from the command palette.
-
-**tmux preset** — remaps pane/tab actions onto a prefix (default ⌃ B):
-⌃ BC opens a tab, ⌃ B% splits,
-⌃ B then an arrow moves focus. A bare prefix reaches the shell after
-a brief pause; `prefix` + an unbound key passes straight through.
-
-## Performance notes
-
-- The PTY is read at device speed and parsed in large batches, off the render path
-- Hot paths are lock-free — a big `cat` never waits on drawing
-- The server buffers up to 16 MiB ahead of the window before backpressure applies
-
-## macOS privacy
-
-Panes are forked from the bundled executable, so macOS attributes a program's
-request for a protected resource to tty7.app. tty7 declares the matching TCC
-usage strings (camera, microphone, contacts, calendar, reminders, photos,
-location, local network, Bluetooth, speech recognition, Apple Events, system
-administration) so that program gets the normal one-time prompt instead of
-being denied outright with no prompt at all.
-
-Not covered by usage strings:
-
-- **Full Disk Access** — Apple defines no usage-string key for it. Reaching
- `~/Library/Mail`, `~/Library/Messages`, `~/Library/Safari` or
- `~/Library/Containers` needs a manual grant in System Settings.
-
-Declaring a usage string is not the same as holding the permission: tty7.app
-itself is granted none of these resources. Every prompt you see belongs to
-whatever you ran in the pane, and you can revoke it under Privacy & Security.
-
-## Localization
-
-The GUI ships English, Simplified Chinese and Japanese strings. Pick one in
-Settings → Appearance → Language, or in `config.json`:
-
-```json
-{ "gui_language": "zh-CN" }
-```
-
-`en`, `zh-CN` and `ja-JP` are the only accepted values; anything else falls back
-to `en`.
-The choice is explicit — the system language is never inferred. CLI output stays
-English so agent and script integrations keep a stable, predictable surface.
diff --git a/docs/features.zh-CN.md b/docs/features.zh-CN.md
deleted file mode 100644
index abeda9e7..00000000
--- a/docs/features.zh-CN.md
+++ /dev/null
@@ -1,150 +0,0 @@
-# 功能
-
-[English](features.md) · 简体中文
-
-## 输入
-
-- **影子建议** —— 边打字边用你的历史补全整条命令,→ 接受
-- **带说明的 Tab 补全** —— 每个 flag、每个子命令都带说明,覆盖约 100 个常用命令;tty7 没有候选时 Tab 自动交给 shell 自己的补全,整个功能也可关闭(设置 → 输入 → 提示符,或 `config.json` 里的 `tab_completion`)
-- **语法高亮** —— 边打边亮,什么都不用装
-- **模糊历史搜索** —— ⌃ R 看到每条命令在哪跑的、什么时候、有没有失败;关掉它(设置 → 输入 → 提示符,或 `config.json` 里的 `history_search`)后 ⌃ R 直接交给 shell,你绑的 fzf / percol 照常可用
-- **历史开箱即用** —— 你已有的 shell 历史直接生效,并跨会话延续
-- **行编辑** —— 点击定位光标、鼠标选区、词级移动、撤销
-- **多行编辑** —— 折行和多行命令原地编辑;网格自动上移,光标始终可见。⇧ ⏎ · ⌥ ⏎ 插入换行而不提交(可改绑,动作名 `InsertNewline`),单独按 ⏎ 提交整个缓冲区
-
-## 窗口
-
-- **标签页与分屏** —— 永远开在当前目录
-- **拖动重排分屏** —— 鼠标移到某个 pane 上,它顶边中间会浮出一个小抓手;拖着它在布局里走,就能把这个 pane 挪到标签页内的别处。落在某个 pane 的某一侧=插到它旁边:那一侧要是朝着同一排的邻居,就并入那一排、和它们等分;要是横着切过这一排(没有排可并),才是把那个 pane 一分为二、自己占住那一半。落在它正中=两个 pane 互换位置;继续推到某个 pane 朝着窗口那一侧的外缘(不是朝着另一个 pane 的那侧)=变成贴着窗口某一边、跨满整行或整列的一条,宽度按那条轴上已有的份数均分 —— 2×2 里的一个 pane 一次拖动就能变成通高的第三列(各占三分之一),而不是独占半屏。拖动过程中落点会高亮,且只有当这一放确实会改变布局时才会亮
-- **侧栏按仓库分组** —— 左侧标签栏按 git 仓库分组、每组一个标题行,不在仓库里的标签归入末尾的 *草稿* 组;切分支、仓库内 `cd` 都不会挪动行(`config.json` 的 `sidebar_grouping`:默认 `repo`,`none` 恢复扁平列表)
-- **命令面板** ⌘ P · scrollback 搜索 ⌘ F
-- **⌘ 点击打开链接** · 桌面通知 · 划选即复制(可选,设置 → 输入 → 选择与剪贴板)
-- **智能双击选中** —— 双击直接选中整条 URL、文件路径、括号/引号对,中文按词典分词出词;Shift 点击扩展选区(设置 → 输入 → 选择与剪贴板可开关;分隔符用 `config.json` 的 `word_separators` 配置)
-- **9 套主题,也能自定义** — YAML 种子主题,背景支持纯色、渐变或图片;可导入 iTerm2 `.itermcolors`;应用内颜色编辑器带背景图选择
-- **跟随系统外观** — 设置 → 外观;分别选好浅色和深色主题,tty7 随系统深浅模式实时切换(`config.json` 中的 `theme_follow_system`、`theme_preset_light` / `theme_preset_dark`)
-- **窗口透明与模糊** — 设置 → 外观 → 透明度;对所有主题生效,*跟随主题* 恢复主题自带的 `opacity` / `blur`
-- **CJK / 输入法输入**
-- **Windows 资源管理器右键菜单** —— 安装程序提供 *Add “Open in tty7” to the folder context menu* 这个安装任务,默认不勾选,卸载时一律移除。写 shell verb 是安装期的决定,所以没有运行时开关;用 portable zip 的话可以自己执行 `tty7-app.exe --register-explorer-menu`(或 `--unregister-explorer-menu`)。两种方式写入的键都在 `HKCU` 下,只影响你自己的 Windows 账户
-
-## 字体
-
-- **内置 Hack** —— 打包进二进制,默认配置在各平台渲染完全一致,不依赖系统安装
-- **主字体 + 有序 fallback** —— `config.json` 里的 `font_family` 和 `font_fallbacks`;可选 `font_family_bold` / `font_family_italic` 指定独立字面,`font_features` 透传 OpenType 特性(上下文连字默认关闭)
-- **默认列表按平台分支** —— fallback 只写宿主系统真正自带的字体(macOS 用 PingFang SC / Apple Color Emoji,Windows 用 Microsoft YaHei / Segoe UI Emoji,Linux 用 Noto)。这些名字也会追加到你手写的列表后面,所以在别的平台写出来的 `config.json` 一样能落地
-
-### 中文与两列网格
-
-一个格子等于主字体的一个 advance,宽字符(CJK)被钉死在正好两格上。所以中文
-fallback 只有在**汉字 advance 等于主字体西文 advance 的两倍**时,才能严丝合缝地
-填满自己的槽。
-
-内置 Hack 的 advance 是 0.60205em,两格就是 1.2041em —— 而系统自带的中文字体
-(Microsoft YaHei、PingFang SC、Noto Sans CJK)全都是 1.0em。这些字形在槽里左
-对齐,多出来的约 0.2em 就变成每个字右边的一道空隙。
-
-[Maple Mono NF CN](https://github.com/subframe7536/maple-font) 在所有平台都排在
-第一位正是因为这个 —— 西文 0.6em、中文 1.2em,对上 Hack 正好两格。它只按名字引
-用,不打包(每字重约 20MB):装上即生效,不用改配置。
-
-想让中文排得**紧**而不只是均匀,要换的是主字体:选一个 advance 为 0.5em 的
-(比如 Sarasa Mono SC 更纱黑体等宽),两格就正好 1.0em。
-
-## Coding agent
-
-tty7 能识别 pane 里跑着的第三方 coding agent(Claude Code、Codex、Gemini CLI、
-Aider、Amp、OpenCode 等共 18 个)并在其外围加功能 —— 绝不包裹或替代 agent 本身。
-
-- **品牌头像** —— 标签 chip / 侧栏行显示每个 pane 跑的是哪个 agent;自定义包装命令可通过 `config.json` 的 `agent_commands` 映射
-- **状态点** —— 工作中(蓝)/ 等你输入(琥珀)/ 完成(绿),由 agent 自己上报的 OSC 事件驱动;在 设置 → Agents 一键装好对应 hooks(Claude Code、Codex、Copilot CLI、OpenCode、Pi、Grok Build、Oh My Pi)
-- **通知** —— agent 卡在等你批准的那一刻弹 "needs your permission…",每轮结束弹 "finished after Ns",遵循你的通知策略
-- **一眼看分支** —— 侧栏每行显示该 pane 的 git 分支和工作区改动(`+N −M`),`cd` 或命令跑完时自动刷新;点改动数字会打开 diff 浮层,关掉它(设置 → 窗口与标签页,或 `config.json` 的 `sidebar_diff_preview: false`)分支和数字照常显示,只是不再可点
-- **会话恢复** —— 重启后无法重连的 pane 会自动续上 agent 对话,并带上原始启动 flags(`claude --dangerously-skip-permissions --resume …`;`restore_agent_sessions`,默认开启)
-- **Fork 会话** —— 直接调 agent 自己的 fork 命令(`codex fork `、`claude --resume --fork-session`,OpenCode、Grok Build 和 Oh My Pi 同样支持),把当前对话分叉成一个独立会话;原会话原封不动,两边各自往下走。在 pane 上右键可选择分屏位置,在标签 / 侧栏行上右键则直接开新标签。需要先装好该 agent 的 hooks(fork 认的是 hooks 上报的 session id);远程 pane 不能 fork,因为命令会跑在本机的 agent 上;另外 fork 会整份复制对话历史,反复 fork 会在 agent 自己的会话目录里占掉不少磁盘
-- **复制会话 ID** —— 把 agent 的原生 session id 复制到剪贴板,就在 *复制工作目录* 旁边,方便粘进 `codex resume`、bug 报告或别的工具
-- **上下文回填** —— 面板命令把当前选区或仓库 `git diff` 打包成 prompt 直接喂给正在跑的 agent
-- **托盘图标** —— 系统托盘 / 菜单栏常驻图标,任何 agent 等你输入时立即切换为提醒态;菜单列出所有 agent pane(品牌头像 + 状态点,点击直达)、可切换通知策略,并在保留会话的普通退出之外提供 *退出并停止服务器…*(`show_tray_icon`,默认开启)
-- **`tty7 wait`** —— CLI 的编排原语:阻塞到某个 pane 的 agent 等待输入或完成一轮(`tty7 wait %3 --until waiting,done --changed --timeout 600`,超时退出码 124),让一个 agent 睡到同伴卡在权限确认的那一刻,而不是抓屏猜——然后 `tty7 capture %3 --plain` 收结果。agent 状态是电平不是边沿,所以 `--changed` 会忽略 wait 开始时 pane 本来就处在的那个状态;不加它的话,JSON 里的 `stale` 标记会告诉你这个答案是不是上一轮留下的
-- **`tty7` 上 PATH** —— CLI 随每个安装包一起发布,启动时自动放到 PATH 上,脚本和 coding agent 在任何终端里都能驱动 tty7。tty7 自己的 pane 里则一定可用,因为 pane 继承 app 的环境。Unix 上是往 `/opt/homebrew/bin`、`/usr/local/bin`、`~/.local/bin`、`~/bin`、`~/.cargo/bin` 中你 PATH 已经覆盖的那个目录里放一个软链;Windows 上是把安装目录追加到用户 PATH,卸载时再摘掉。你自己装的 `tty7` 一律保持原样,不会被覆盖。关掉:设置 → Agents,或 `config.json` 里 `install_cli_on_path: false`
-
-## SSH
-
-**唯一**路径就是原生 Rust SSH 栈(russh)—— profile、凭据、SFTP 全部内置,
-不 shell 出 `ssh`,也没有系统 ssh 兼容模式。
-
-- **QuickConnect** —— 面板里打 `user@host[:port]` 回车即连;支持 IPv6 `[::1]:port`
-- **保存 profile** —— 完整连接配置,密码 / passphrase 进 OS keychain,不落盘
-- **`~/.ssh/config` alias** —— 直接输入 alias 即连(原生解析常用字段,尽力而为,走 russh),也可在设置页一键导入为 profile
-- **GUI 认证** —— pane 内 sheet 输入密码、私钥 passphrase、2FA,并确认主机密钥(新主机 vs 已变更)
-- **内置 SFTP** —— 滑入式文件面板:浏览、上传 / 下载、重命名 / 删除 / chmod,可拖进 Finder
-- **端口转发** —— Local / Remote / Dynamic,预配置或运行时增删,外加 ⌘ 点击 `localhost:PORT` 一键转发
-- **跳板与代理** —— 经 profile 引用或 `ProxyJump` 多跳、ProxyCommand、SOCKS5 / HTTP
-
-| 入口 | 连接方式 |
-|---|---|
-| 保存 profile · QuickConnect · 输入 `user@host[:port]` | 原生 russh —— SFTP · keychain · GUI 认证 · L/R/D 转发 |
-| `~/.ssh/config` alias | 原生解析后走 russh(`Match`/canonicalize/GSSAPI 不支持,且无回退) |
-
-## 快捷键
-
-下表按 macOS 记法书写 —— 在 Windows 和 Linux 上,把 ⌘ 读作
-Ctrl。最常用的几个:
-
-| | |
-|---|---|
-| ⌘ T · ⌘ W · ⌘ ⇧ T | 新建标签页 · 关闭标签页 · 恢复关闭的标签页 |
-| ⌘ 1…⌘ 9 | 跳到第 1–9 个标签页 |
-| ⌃ ⇥ · ⌃ ⇧ ⇥ | 按住不放在切换面板里向后 · 向前走,松手即切换 |
-| ⌘ D · ⌘ ⇧ D | 向右分屏 · 向下分屏 |
-| ⌘ ] · ⌘ [ | 下一个窗格 · 上一个窗格 |
-| ⌘ ⌥ ←→↑↓ | 按方向切换焦点窗格 |
-| ⌘ ⏎ · ⌘ ⇧ ⏎ | 切换全屏 · 缩放窗格 |
-| ⌘ K | 清除 scrollback |
-| ⌘ P | 命令面板 |
-| ⌘ F | 搜索 scrollback |
-| ⌃ R | 模糊搜索 shell 历史 |
-| ⌘ + · ⌘ − · ⌘ 0 | 字号增大 · 减小 · 重置 |
-| ⌘ + 滚轮 | 在终端上滚动缩放字号,演示时随手放大 |
-
-**设置 → 按键绑定**(⌘ ,)列出全部快捷键。点一行、按下新键即可
-(Esc 取消,Backspace 恢复默认),改完立即生效。窗格缩放与
-交换默认不绑定键 —— 在这里绑定,或从命令面板执行。
-
-**tmux 预设** —— 把窗格/标签页操作映射到前缀键(默认 ⌃ B):
-⌃ BC 新建标签页,⌃ B% 分屏,
-⌃ B 接方向键切换焦点。单独按前缀键会在短暂延迟后送达 shell,
-`前缀` + 未绑定的键原样透传给终端。
-
-## 性能说明
-
-- 以设备速度读取 PTY,在渲染路径之外成批解析
-- 热路径全程无锁 —— 再大的 `cat` 也不会阻塞在渲染上
-- 触发背压前,服务器最多可领先窗口缓冲 16 MiB
-
-## macOS 隐私
-
-窗格是从 app bundle 里的可执行文件 fork 出来的,所以程序申请受保护资源时,
-macOS 会把这次请求算到 tty7.app 头上。tty7 声明了对应的 TCC usage strings
-(摄像头、麦克风、通讯录、日历、提醒、照片、定位、本地网络、蓝牙、语音识别、
-Apple Events、系统管理),这样程序才能正常弹出一次性授权窗口,而不是连弹窗都
-没有就被直接拒绝。
-
-不受 usage strings 覆盖的:
-
-- **完全磁盘访问** —— 苹果没有为它定义 usage-string 键。要读写
- `~/Library/Mail`、`~/Library/Messages`、`~/Library/Safari` 或
- `~/Library/Containers`,需要在「系统设置」中手动授权。
-
-声明 usage string 不等于持有权限:tty7.app 自己一项都没有拿到。你看到的每个
-授权弹窗都属于你在窗格里运行的那个程序,也可以在「隐私与安全性」中撤销。
-
-## 本地化
-
-GUI 目前提供英文、简体中文和日文三套文案。在「设置 → 外观 → 语言」中选择,或直接改
-`config.json`:
-
-```json
-{ "gui_language": "zh-CN" }
-```
-
-只接受 `en`、`zh-CN` 和 `ja-JP` 三个值,其它值一律回落到 `en`。语言必须显式指定,不会
-去猜系统语言。CLI 输出保持英文,保证 agent、脚本和开发者工作流的输出稳定可预测。
diff --git a/docs/getting-started/concepts.mdx b/docs/getting-started/concepts.mdx
new file mode 100644
index 00000000..1f6bb2ec
--- /dev/null
+++ b/docs/getting-started/concepts.mdx
@@ -0,0 +1,108 @@
+---
+title: "Core concepts"
+description: "Workspaces, tabs, panes — and the background server that owns them all."
+---
+
+Four words explain most of tty7. Three of them you can see; the fourth is the
+reason the other three survive a reboot.
+
+## Pane
+
+A **pane** is one terminal: one shell (or one program) attached to one PTY. It
+is the only thing in tty7 that actually runs something.
+
+Panes have stable ids — `%42` — for their whole life. That id is what the
+[CLI](/cli/overview) addresses, and what `$TTY7_PANE` holds inside the pane
+itself.
+
+## Tab
+
+A **tab** is a layout of panes. One pane to start with; split it and the tab
+holds two, arranged in rows and columns you can drag around.
+
+Tabs appear in the sidebar (or the top strip, if you move it there). A tab's
+label is the best evidence tty7 has: a name you set, else the coding agent
+running in it, else the last segment of its working directory.
+
+## Workspace
+
+A **workspace** is a named set of tabs — a project, usually. One window shows
+one workspace at a time, and ⌘ ⇧ O opens the switcher to move between
+them or open a second window on another one.
+
+Workspaces are how tty7 keeps ten repositories from becoming forty
+indistinguishable tabs. They also travel: a workspace on a remote machine is
+still a workspace, opened from the same switcher.
+
+
+
+
+
+## The server
+
+Here is the part that matters. **The window does not own your shells — a
+background server does.**
+
+Quitting tty7 closes the window and leaves that server running. Your build keeps
+building, your agent keeps working, your SSH session stays up. Open tty7 again
+and it reattaches to exactly what was there.
+
+This is also why:
+
+- **`tty7` works from any terminal.** The CLI talks to the same server. The GUI
+ does not have to be running at all.
+- **A crash is not a catastrophe.** Panes come back showing what was on them:
+ a capped tail of each pane's output is kept on disk and handed to the pane
+ that reopens on its id.
+- **Stopping is explicit.** *Quit and Stop Server…* in the tray menu is the only
+ ordinary way to end everything, and it warns you first.
+
+
+ Restarting the server ends every process in every pane on that machine —
+ shells, agents, and SSH sessions alike. Layouts are kept and come back with
+ fresh shells. Never do it on someone else's behalf without asking.
+
+
+### What survives what
+
+| | Close a tab | Quit tty7 | Stop the server | Reboot |
+|---|:--:|:--:|:--:|:--:|
+| The shell keeps running | ✗ | ✓ | ✗ | ✗ |
+| The layout comes back | ✗ | ✓ | ✓ | ✓ |
+| What was on screen comes back | ✗ | ✓ | ✓ | ✓ 1 |
+| A supported agent session resumes | ✗ | ✓ | ✓ | ✓ |
+
+1 A capped tail of each pane, restored once. See
+[session restore](/reference/troubleshooting#panes-came-back-empty).
+
+## Machines
+
+Everything above exists per **machine**. Your laptop is one; a dev box you
+connect to over SSH is another, with its own server, its own workspaces, and its
+own panes.
+
+The switcher lists them together, and the CLI reaches them with `-m`:
+
+```bash
+tty7 -m devbox ls
+```
+
+Remote panes run on the remote machine — the files, the repository, the git
+data, and the process tree are all over there.
+[Remote workspaces →](/remote/workspaces)
+
+## The three environment variables
+
+Every pane exports these, and anything you launch from one inherits them:
+
+| Variable | What it holds |
+|---|---|
+| `TTY7_PANE` | This pane's id — the default target of `tty7 split`, `send`, `capture`, `procs`. |
+| `TTY7_WS` | This pane's workspace id. |
+| `TTY7_CONFIG_DIR` | The config directory, which is how the CLI finds the right server. |
+
+`echo $TTY7_PANE` is the fastest way to tell whether you are inside tty7 at all.
+
+
+ Those ids are the whole interface. The CLI page starts there.
+
diff --git a/docs/getting-started/first-launch.mdx b/docs/getting-started/first-launch.mdx
new file mode 100644
index 00000000..cb83ceb7
--- /dev/null
+++ b/docs/getting-started/first-launch.mdx
@@ -0,0 +1,126 @@
+---
+title: "First launch"
+description: "The handful of settings worth changing before you start working."
+---
+
+Open tty7 and you get a window with one tab and one shell, and a tab sidebar
+down the left. Everything below is optional — but these are the settings people
+end up changing anyway, so they are worth five minutes now.
+
+Open Settings with ⌘ , (Ctrl , on Windows and Linux), or
+from the command palette (⌘ P → *Settings*).
+
+
+
+
+
+## 1. Pick a theme
+
+**Settings → Appearance → Theme.** Nine themes ship built in — Light, One Light,
+Catppuccin Latte, Rosé Pine Dawn, Dark, Dracula, Harbor, One Dark Pro, and
+Rosé Pine. The default is **Light**.
+
+Turn on **Sync with system** to pick a light theme and a dark theme separately;
+tty7 then follows the OS appearance live.
+
+Transparency lives on the same page, under **Transparency** — opacity applies to
+every theme, and *Follow theme* hands the decision back to the theme's own
+setting. On Windows there is also a **Background material** picker (Mica,
+Acrylic, and friends).
+
+[More about themes →](/customization/themes)
+
+## 2. Choose your shell
+
+**Settings → Terminal → Shell.** Leave **Program** empty to use the platform
+default. Otherwise it takes an executable name on PATH or an absolute path
+(`zsh`, `fish`, `pwsh`, `nu`, `/opt/homebrew/bin/bash`), plus space-separated
+**Arguments** — `-l` for a login shell, say.
+
+**Start in** decides what a *fresh* shell opens in: tty7's launch directory
+(the default), your home folder, or a fixed path. New tabs and splits keep
+inheriting the active pane's directory either way.
+
+## 3. macOS only: decide what Option does
+
+**Settings → Input → Keyboard → Option (⌥) acts as Meta.**
+
+Off (the default), ⌥ B types `∫`, which is what macOS has always
+done. On, it sends the escape chord shells expect, so ⌥ B moves back
+a word and ⌥ ⌫ deletes one. Turn it on if you live in readline;
+leave it off if you type accented characters.
+
+## 4. If you use coding agents, install the hooks
+
+**Settings → Agents.** tty7 detects 18 coding CLIs by process name on its own —
+you get brand avatars and tab labels for free. The *status dots*, the "needs
+your permission" notifications, and `tty7 wait` all need one more thing: a small
+hook the agent calls to report what it is doing.
+
+Click **Install** next to Claude Code, Codex, Copilot CLI, OpenCode, Pi, Grok
+Build, or Oh My Pi. It writes into that agent's own config directory and can be
+removed from the same row.
+
+[More about agents →](/agents/status)
+
+## 5. Know what Quit does
+
+Plain **Quit** closes the window and leaves the background server running.
+Your shells, builds, and agent turns keep going, and reopening tty7 reattaches
+to them.
+
+To actually stop everything, use **Quit and Stop Server…** from the tray icon's
+menu. It says so plainly before it does it: anything still running in your
+shells is terminated, while your tabs and layout are kept and reopen with fresh
+shells.
+
+
+ This is why there is no tmux in the picture. The persistence is not a feature
+ of your shell setup — it belongs to the server underneath.
+ [Core concepts →](/getting-started/concepts)
+
+
+## 6. Tune the notifications
+
+**Settings → Window & Tabs → Notifications.** By default tty7 posts a desktop
+notification when a foreground command that ran longer than 10 seconds
+finishes — but only while the window is unfocused. Set **Notify on command
+finish** to *Never* or *Always*, and move the threshold if 10 seconds is the
+wrong number for your work.
+
+Agent notifications ("needs your permission…", "finished after 42s") follow the
+same policy.
+
+## 7. Coming from tmux?
+
+**Settings → Keybindings → Preset → tmux** remaps pane and tab actions onto a
+prefix, ⌃ B by default. ⌃ BC opens a tab,
+⌃ B% splits, ⌃ B then an arrow moves focus.
+
+A bare prefix reaches the shell after about a second, and prefix plus an unbound
+key passes straight through — so a tmux binding you did not remap still lands in
+whatever is running.
+
+[More about keybindings →](/customization/keybindings)
+
+## Where things live
+
+| | macOS / Linux | Windows |
+|---|---|---|
+| Settings file | `~/.config/tty7/config.json` | `%APPDATA%\tty7\config.json` |
+| Custom themes | `~/.config/tty7/themes/` | `%APPDATA%\tty7\themes\` |
+
+Everything in the Settings window writes to `config.json`, and you can edit it
+by hand instead — see the [configuration reference](/reference/configuration).
+Set `TTY7_CONFIG_DIR` to move the whole directory somewhere else.
+
+## Next
+
+
+
+ Workspaces, tabs, panes, and the server that owns them.
+
+
+ Suggestions, completion, and history search — the part you touch most.
+
+
diff --git a/docs/getting-started/installation.mdx b/docs/getting-started/installation.mdx
new file mode 100644
index 00000000..4f5f3e59
--- /dev/null
+++ b/docs/getting-started/installation.mdx
@@ -0,0 +1,151 @@
+---
+title: "Installation"
+description: "Native builds for macOS, Windows, and Linux — plus building from source."
+---
+
+Every release publishes native builds on
+[**GitHub Releases**](https://github.com/l0ng-ai/tty7/releases). There is no
+runtime to install first: fonts are embedded in the binary, and the Linux
+AppImage bundles its own X11/Wayland/font libraries.
+
+
+
+ Download the DMG that matches your Mac and drag **tty7** into Applications.
+
+ | Mac | File |
+ |---|---|
+ | Apple silicon (M1 and later) | `tty7--macos-arm64.dmg` |
+ | Intel | `tty7--macos-x86_64.dmg` |
+
+ Builds are signed with a Developer ID certificate and notarized by Apple, so
+ Gatekeeper opens them without a right-click dance.
+
+
+ Builds are produced on macOS 14 and macOS 15. macOS 14 (Sonoma) or later
+ is the tested range.
+
+
+
+
+ Two shapes, both x86-64:
+
+ | File | Use it when |
+ |---|---|
+ | `tty7--windows-x86_64-setup.exe` | You want a normal install with Start-menu entries and an uninstaller. |
+ | `tty7--windows-x86_64.zip` | You want it portable — unzip anywhere and run `tty7-app.exe`. |
+
+ The installer offers one optional setup task, off by default: **Add "Open in
+ tty7" to the folder context menu**. It writes shell verbs under `HKCU`, so
+ only your own Windows account is affected, and the uninstaller always takes
+ them back out.
+
+ A portable install can add or remove the same entries itself:
+
+ ```powershell
+ tty7-app.exe --register-explorer-menu
+ tty7-app.exe --unregister-explorer-menu
+ ```
+
+
+ The Windows package also carries a Linux `tty7-server` binary so a WSL
+ distro can be served locally instead of downloading one. See
+ [Remote workspaces](/remote/workspaces).
+
+
+
+
+ | File | Use it when |
+ |---|---|
+ | `tty7--linux-x86_64.AppImage` | Almost always. `chmod +x` and run — the X11, Wayland, xkb, and font libraries are bundled, so it works on Fedora, Arch, Debian and friends, not just Ubuntu. |
+ | `tty7--linux-x86_64.tar.gz` | You would rather unpack the plain binary and place it yourself. |
+
+ ```bash
+ chmod +x tty7-*-linux-x86_64.AppImage
+ ./tty7-*-linux-x86_64.AppImage
+ ```
+
+
+
+## The `tty7` command
+
+Every installer ships the `tty7` CLI beside the app, and the app puts it on your
+PATH the first time it launches. That is what lets a script — or a coding agent
+in some other terminal — open panes and read them back.
+
+- **On Unix** it is a symlink into whichever of `/opt/homebrew/bin`,
+ `/usr/local/bin`, `~/.local/bin`, `~/bin`, or `~/.cargo/bin` your PATH already
+ covers.
+- **On Windows** the install directory is appended to your user PATH, and the
+ uninstaller removes it again.
+
+A `tty7` you installed yourself — a `cargo install` build, a package manager's
+copy — is never replaced. To turn the whole thing off, uncheck **Settings →
+Agents → Install the tty7 command on PATH**.
+
+
+ Inside a tty7 pane the CLI works regardless of PATH, because panes inherit the
+ app's environment.
+
+
+## Updating
+
+tty7 checks for updates every six hours and can update itself: **Settings →
+About → Check now**, then **Update and relaunch**. Releases are downloaded and
+verified in the background so applying one is just a restart.
+
+Pick **Stable** or **Nightly** under **Settings → About → Update channel**. See
+[Updates and channels](/reference/updates) for what each feed publishes and how
+switching behaves.
+
+## Building from source
+
+You need a stable Rust toolchain. The build is a plain `cargo build`; the app
+binary is `tty7-app`.
+
+
+
+ ```bash
+ git clone https://github.com/l0ng-ai/tty7
+ cd tty7
+ cargo build --release
+ ```
+
+
+ gpui resolves its X11/Wayland/font backends through `pkg-config` at build
+ time, so the development packages have to be present:
+
+ ```bash
+ sudo apt-get install -y pkg-config cmake clang \
+ libxkbcommon-dev libxkbcommon-x11-dev \
+ libfontconfig1-dev libfreetype6-dev \
+ libwayland-dev libx11-dev libxcb1-dev \
+ libzstd-dev libssl-dev libkrb5-dev
+ cargo build --release
+ ```
+
+
+
+
+ A source build does not update itself, and it will not replace an installed
+ copy's server. If you run both, see
+ [Troubleshooting](/reference/troubleshooting).
+
+
+## Uninstalling
+
+
+
+ Quit tty7 (use **Quit and Stop Server** from the tray menu so the background
+ server stops too), then drag the app to the Trash. Your settings live in
+ `~/.config/tty7` and are left alone; delete that folder to remove them.
+
+
+ Use **Add or remove programs**. The uninstaller removes the PATH entry and
+ any Explorer context-menu keys it added. Settings live in
+ `%APPDATA%\tty7`.
+
+
+ Delete the AppImage or the unpacked directory. Settings live in
+ `~/.config/tty7`.
+
+
diff --git a/docs/git/diffs.mdx b/docs/git/diffs.mdx
new file mode 100644
index 00000000..5c2441f3
--- /dev/null
+++ b/docs/git/diffs.mdx
@@ -0,0 +1,48 @@
+---
+title: "Diffs"
+description: "The diff overlay: side-by-side or unified, from the sidebar or the panel."
+---
+
+## Opening one
+
+| From | How |
+|---|---|
+| The sidebar | Click a row's `+N −M` counts |
+| Source Control | **Open Changes** on a file, or click the row |
+| History | Click a file inside a commit's detail view |
+
+The overlay covers the window; Esc closes it.
+
+
+
+
+
+## Side-by-side or unified
+
+**Toggle Unified / Side-by-Side Diff** in the command palette switches between
+the two. The choice is global — one setting for every diff, the same call VS
+Code's `diffEditor.renderSideBySide` makes — and persists as `diff_view` in
+`config.json`.
+
+## What it shows
+
+- Every changed file, with its status and `+N −M`
+- **Untracked files** as a preview of their contents, up to 4 MB — past that the
+ card says the read failed rather than showing a silently cut-off file
+- A commit's files, when the diff came from the history
+
+Two limits keep a huge diff from becoming a huge wait:
+
+| Limit | Value | What happens |
+|---|---|---|
+| Files rendered | 300 | *"Showing the first 300 of N changes."* |
+| Lines before auto-collapse | 400 per file | Big files start collapsed; expand the ones you care about |
+
+Both are stated in the overlay when they apply — nothing is dropped silently.
+
+## Turning the sidebar shortcut off
+
+If you would rather the sidebar's counts not be clickable, turn off **Settings →
+Window & Tabs → Open diff preview from sidebar counts**
+(`sidebar_diff_preview: false`). The branch and counts stay on the row; they
+just stop opening the overlay.
diff --git a/docs/git/source-control.mdx b/docs/git/source-control.mdx
new file mode 100644
index 00000000..da3f23f2
--- /dev/null
+++ b/docs/git/source-control.mdx
@@ -0,0 +1,86 @@
+---
+title: "Source control"
+description: "Stage, commit, branch, and push from the panel beside your terminal."
+---
+
+The **Source Control** tab of the [side panel](/window/side-panel) (⌘ J)
+is a full git client for whichever repository the focused pane is in. It follows
+the pane: `cd` into another repository and the panel switches with you.
+
+
+
+
+
+## Changes
+
+Files are grouped by what git thinks of them:
+
+| Group | |
+|---|---|
+| **Merge Changes** | Conflicts, with *Resolve Conflict* and *Mark as Resolved* |
+| **Staged Changes** | What the next commit will contain |
+| **Changes** | Modified but not staged |
+| **Untracked** | New files |
+
+Each row has **Stage Changes**, **Unstage Changes**, **Discard Changes**, and
+**Open Changes** — which opens the [diff](/git/diffs). Group-level *Stage All*,
+*Unstage All*, and *Discard All* sit on the headers, and the destructive ones
+confirm first.
+
+## Committing
+
+Write the message in the box at the top and pick a commit action:
+
+| | |
+|---|---|
+| **Commit** | Commit what is staged |
+| **Commit All** | Stage everything, then commit |
+| **Commit (Amend)** | Replace the last commit — confirms first, because anyone who already has it has to reconcile |
+| **Commit & Push** | Commit, then push |
+| **Commit & Sync** | Commit, then pull and push |
+
+⌘ ⏎ commits while the caret is in the message box. **Stash All** is
+there too.
+
+## Branches and remotes
+
+| | |
+|---|---|
+| **Checkout to…** | Switch branches, with a search box; offers **Stash & Switch** when the tree is dirty |
+| **Create Branch…** | From here, or from any commit in the history |
+| **Publish Branch** | For a branch with no upstream yet |
+| **Sync Changes** | Pull, then push |
+| **Push** · **Pull** · **Fetch** | Individually |
+
+All of these are also in the command palette under **Git**, so they are
+bindable.
+
+When a repository is mid-operation — merging, rebasing, cherry-picking,
+reverting, bisecting, applying — the panel says so instead of pretending
+everything is normal.
+
+## History
+
+**Git: Toggle Commit History** (or the *History* section header) opens the
+commit graph: branches drawn as lanes, a filter box, **Current Branch** or
+**All Branches**, and *Load more* at the bottom.
+
+Click a commit for its detail view — message, parents, and the files it touched,
+each openable as a diff. From a commit's menu:
+
+| | |
+|---|---|
+| **Checkout Commit** · **Create Branch Here…** | Move to it |
+| **Cherry Pick** · **Revert Commit** | Apply or undo it here |
+| **Reset (Soft / Mixed / Hard)** | Move the branch to it — Hard confirms, since commits after it fall off the branch and uncommitted changes are discarded |
+| **Copy Commit SHA** | |
+
+The history section starts collapsed and remembers whether you opened it
+(`scm_graph_expanded`).
+
+## In the sidebar
+
+You do not have to open the panel to know where you stand: every
+[sidebar row](/window/sidebar) carries its pane's branch and a `+N −M` count of
+the working tree, refreshed on `cd` and when a command finishes. Clicking the
+counts opens the diff overlay.
diff --git a/docs/git/worktrees.mdx b/docs/git/worktrees.mdx
new file mode 100644
index 00000000..74d33edb
--- /dev/null
+++ b/docs/git/worktrees.mdx
@@ -0,0 +1,56 @@
+---
+title: "Worktrees"
+description: "An isolated checkout on a fresh branch, in one dialog and one tab."
+---
+
+Running two agents on the same repository at once means they fight over the
+working tree. A git worktree is the fix, and tty7 makes it a single dialog.
+
+## Creating one
+
+**New Worktree Tab…** — in the command palette, the tab's right-click menu, and
+the application menu — asks three things:
+
+| Field | Default |
+|---|---|
+| **Worktree Name** | A fresh name that does not collide with an existing branch or directory |
+| **New Branch** | The same name, editable |
+| **Start From** | The branch you are currently on |
+
+Each field opens on a suggestion you can accept or type straight over.
+
+
+
+
+
+Confirm and tty7 creates the worktree, opens a tab in it, and starts a shell
+there. The [sidebar](/window/sidebar) files it under the same repository group as
+its parent, on its own branch.
+
+## Where they go
+
+Worktrees land inside the repository, under:
+
+```
+/.tty7/worktrees/
+```
+
+`/.tty7/.gitignore` is created with `*` in it the first time, so the
+directory never shows up as an untracked mess in your own repository.
+
+## Removing one
+
+Closing a worktree tab offers to remove the worktree with it:
+
+- **Clean tree** — *Remove Worktree* or *Keep*.
+- **Dirty tree** — the dialog says so, and removing requires the explicit
+ *Discard Changes & Remove*.
+
+Nothing is removed silently, and *Keep* leaves the worktree on disk for `git
+worktree list` to find later.
+
+
+ Pair this with [agent sessions](/agents/sessions): a worktree per agent means
+ two Claude Codes can work on the same repository without stepping on each
+ other's files.
+
diff --git a/docs/images/hero.webp b/docs/images/hero.webp
new file mode 100644
index 00000000..416a5a41
Binary files /dev/null and b/docs/images/hero.webp differ
diff --git a/docs/images/placeholder.svg b/docs/images/placeholder.svg
new file mode 100644
index 00000000..38ee5e64
--- /dev/null
+++ b/docs/images/placeholder.svg
@@ -0,0 +1,12 @@
+
diff --git a/docs/index.mdx b/docs/index.mdx
new file mode 100644
index 00000000..d70cc602
--- /dev/null
+++ b/docs/index.mdx
@@ -0,0 +1,105 @@
+---
+title: "tty7"
+sidebarTitle: "Introduction"
+description: "A terminal workbench: persistent sessions, remote work, agents."
+mode: "wide"
+---
+
+
+
+
+
+tty7 is a terminal you can leave running. Close the window, reboot the machine,
+walk to a different laptop — the shells you started are still there, and so are
+the coding agents you left working in them.
+
+It is written in Rust, renders on the GPU through Zed's
+[gpui](https://github.com/zed-industries/zed), and parses VT with Alacritty's
+terminal core. In practice that means roughly twice the throughput of Alacritty,
+Ghostty, or Kitty on a big `cat`, and a frame rate that does not fall over when
+something floods the screen.
+
+## What makes it different
+
+
+
+ A background server owns your shells, not the window. Quit tty7 and your
+ builds keep building. No tmux to learn or configure.
+
+
+ Ghost suggestions from your history, tab completion that explains each flag,
+ syntax highlighting, click-to-place-caret, real multi-line editing.
+
+
+ 18 coding CLIs are recognised on sight. Per-pane status dots, notifications
+ when one needs you, git context, and session resume after a reboot.
+
+
+ A native Rust SSH stack with profiles, SFTP, and port forwarding — plus
+ remote workspaces where files, repos, and panes all stay on the far machine.
+
+
+ Branch and diff counts on every sidebar row, a source control panel, a diff
+ overlay, and worktrees in one dialog.
+
+
+ A bundled `tty7` CLI that opens panes, sends keys, reads screens, and blocks
+ until an agent needs you — so scripts and agents can drive the workbench.
+
+
+
+## Start here
+
+
+
+ Native builds for macOS, Windows, and Linux.
+ [Installation →](/getting-started/installation)
+
+
+ Five minutes of settings that pay for themselves.
+ [First launch →](/getting-started/first-launch)
+
+
+ Workspace, tab, pane — and the server underneath them.
+ [Core concepts →](/getting-started/concepts)
+
+
+
+## How fast, exactly
+
+Same machine, same day, same 155×40 grid — Apple M1 Pro, macOS 26.3.1,
+five-run averages.
+
+| | **tty7** | Alacritty | Ghostty | Kitty |
+|---|---:|---:|---:|---:|
+| Plaintext I/O — 11 MB `cat` (lower is better) | **95 ms** | 239 ms | 179 ms | 185 ms |
+| [DOOM-fire](https://github.com/const-void/DOOM-fire-zig) frame rate (higher is better) | **888 fps** | 485 fps | 552 fps | 617 fps |
+| Cold-launch memory | 116 MB 1 | 105 MB | 128 MB | 130 MB |
+
+1 GUI 105 MB plus the persistent server at 11 MB.
+
+The methodology and a one-command reproduction live in
+[`scripts/bench/`](https://github.com/l0ng-ai/tty7/tree/main/scripts/bench).
+
+Three decisions account for most of it:
+
+- **The PTY is read at device speed** and parsed in large batches, off the
+ render path — so drawing never throttles reading.
+- **The hot paths are lock-free.** A big `cat` never waits on the renderer.
+- **The server buffers up to 16 MiB** ahead of the window before backpressure
+ applies, which is enough that a flood finishes writing while the window is
+ still catching up.
+
+## Getting help
+
+
+
+ Ask a question, show what you built.
+
+
+ Bugs and feature requests.
+
+
+ Everything that shipped, release by release.
+
+
diff --git a/docs/logo/logo.svg b/docs/logo/logo.svg
new file mode 100644
index 00000000..0ec488f2
--- /dev/null
+++ b/docs/logo/logo.svg
@@ -0,0 +1,6 @@
+
diff --git a/docs/reference/configuration.mdx b/docs/reference/configuration.mdx
new file mode 100644
index 00000000..81851282
--- /dev/null
+++ b/docs/reference/configuration.mdx
@@ -0,0 +1,183 @@
+---
+title: "config.json"
+description: "Every key tty7 reads, its type, and its default."
+---
+
+| | |
+|---|---|
+| macOS / Linux | `~/.config/tty7/config.json` |
+| Windows | `%APPDATA%\tty7\config.json` |
+| Override the whole directory | `TTY7_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
+}
+```
+
+## 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_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). |
+
+[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`. |
+| `sidebar_grouping` | enum | `"repo"` | Or `none` for a flat list. |
+| `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. |
+| `scm_graph_expanded` | bool | `false` | Whether the history section starts open. |
+| `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. |
+| `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_hide_while_typing` | bool | `true` | |
+| `focus_follows_mouse` | bool | `false` | |
+
+## Input and clipboard
+
+| Key | Type | Default | |
+|---|---|---|---|
+| `tab_completion` | bool | `true` | tty7's completion menu on ⇥. Off hands the key to the shell. |
+| `history_search` | bool | `true` | tty7's fuzzy history on ⌃ R. Off hands the key to the shell. |
+| `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` | ⌥+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_command` | string | — | Command for file links. `{path}`, `{line}`, `{column}` are substituted; a flag whose value is missing is dropped. |
+| `ssh_loopback_forward` | bool | `false` | Open `localhost:PORT` links through a temporary forward when the pane is in SSH. |
+
+## 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"}`. |
+| `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 | |
+|---|---|---|---|
+| `ssh_profiles` | array | `[]` | Managed from **Settings → SSH**. Secrets live in the OS keychain, never here. |
+| `verify_host_keys` | bool | `true` | |
+| `ssh_warn_on_close` | bool | `false` | Confirm before closing a live connection. |
+
+## 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
+
+`ssh_profile_frecency` and `command_frecency` record how often and how recently
+you use a profile or command, so the pickers can rank them. They are written by
+the app; there is no reason to edit them.
+
+
+ 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.
+
diff --git a/docs/reference/keyboard-shortcuts.mdx b/docs/reference/keyboard-shortcuts.mdx
new file mode 100644
index 00000000..495e2445
--- /dev/null
+++ b/docs/reference/keyboard-shortcuts.mdx
@@ -0,0 +1,103 @@
+---
+title: "Keyboard shortcuts"
+description: "Every default binding, plus the action names for rebinding."
+---
+
+**Settings → Keybindings** (⌘ ,) is the live version of this page —
+it shows what *your* copy is bound to. This is the shipped default.
+
+## Tabs and workspaces
+
+| Action | macOS | Windows / Linux |
+|---|---|---|
+| New Tab | ⌘ T | Ctrl ⇧ T |
+| Close Pane / Tab | ⌘ W | Ctrl ⇧ W |
+| Reopen Closed Tab | ⌘ ⇧ T | Alt ⇧ T |
+| Next Tab · Previous Tab | ⌃ ⇥ · ⌃ ⇧ ⇥ | same |
+| Go to Tab 1–9 | ⌘ 1…⌘ 9 | Alt 1…Alt 9 |
+| New Workspace | ⌘ ⇧ N | Ctrl ⇧ N |
+| Switch Workspace | ⌘ ⇧ O | Ctrl ⇧ O |
+
+## Panes
+
+| Action | macOS | Windows / Linux |
+|---|---|---|
+| Split Right | ⌘ D | Ctrl ⇧ D |
+| Split Down | ⌘ ⇧ D | Ctrl Alt ⇧ D |
+| Next Pane · Previous Pane | ⌘ ] · ⌘ [ | Ctrl ⇧ ] · Ctrl ⇧ [ |
+| Focus Pane Left / Right / Up / Down | ⌘ ⌥ ←→↑↓ | Alt ←→↑↓ |
+| Zoom Pane | ⌘ ⇧ ⏎ | Ctrl ⇧ ⏎ |
+| Enter Full Screen | ⌘ ⏎ | F11 |
+
+## View
+
+| Action | macOS | Windows / Linux |
+|---|---|---|
+| Command Palette | ⌘ P | Ctrl ⇧ P |
+| Toggle Left Sidebar | ⌘ B | Ctrl ⇧ B |
+| Toggle Right Panel | ⌘ J | Ctrl ⇧ J |
+| Toggle Code Panel | ⌘ ⇧ E | Ctrl ⇧ E |
+| Font Size Up · Down · Reset | ⌘ + · ⌘ − · ⌘ 0 | Ctrl + · Ctrl − · Ctrl 0 |
+| Zoom the font | ⌘ + wheel | Ctrl + wheel |
+
+## Terminal
+
+| Action | macOS | Windows / Linux |
+|---|---|---|
+| Find in Terminal | ⌘ F | Ctrl ⇧ F |
+| Find Next · Previous | ⌘ G · ⌘ ⇧ G | F3 · ⇧ F3 |
+| Clear Scrollback | ⌘ K | Ctrl ⇧ K |
+| Copy · Paste | ⌘ C · ⌘ V | Ctrl ⇧ C · Ctrl ⇧ V · ⇧ Insert |
+| Insert Newline (at the prompt) | ⇧ ⏎ · ⌥ ⏎ | same |
+| Fuzzy history search | ⌃ R | same |
+| Accept ghost suggestion | → | same |
+| Completion menu | ⇥ | same |
+
+## Git and SSH
+
+| Action | macOS | Windows / Linux |
+|---|---|---|
+| Commit (caret in the message box) | ⌘ ⏎ | Ctrl ⏎ |
+| Save (in the editor) | ⌘ S | Ctrl S |
+| Restart SSH Session | ⌘ ⇧ R | Ctrl ⇧ R |
+
+## Application
+
+| Action | macOS | Windows / Linux |
+|---|---|---|
+| Settings | ⌘ , | Ctrl , |
+| Keyboard Shortcuts | ⌘ / | — |
+| Hide tty7 · Hide Others · Minimize | ⌘ H · ⌘ ⌥ H · ⌘ M | — |
+| Quit | ⌘ Q | Ctrl ⇧ Q |
+
+## Actions with no default key
+
+All of these are in the command palette, and all are bindable under **Settings →
+Keybindings**:
+
+| Group | Actions |
+|---|---|
+| Tabs | `RenameTab` · `NewWorktreeTab` · `CloseOtherTabs` · `CloseTabsToTheRight` · `CopyWorkingDirectory` · `MarkTabUnread` · `ToggleTabSidebar` |
+| Panes | `ResizePaneLeft/Right/Up/Down` · `SwapPaneNext` · `SwapPanePrev` |
+| Workspaces | `SelectWorkspace1`…`SelectWorkspace9` · `RenameWorkspace` · `StopWorkspace` · `DeleteWorkspace` |
+| Agents | `ForkAgentSession` (+ `Right` / `Left` / `Down` / `Up`) · `CopyAgentSessionId` |
+| Git | `ScmStageAll` · `ScmUnstageAll` · `ScmDiscardAll` · `ScmCommitAmend` · `ScmRefresh` · `ScmSync` · `ScmPush` · `ScmPull` · `ScmFetch` · `ScmCheckoutBranch` · `ScmCreateBranch` · `ScmToggleGraph` · `ToggleDiffViewMode` |
+| Panels | `ShowRightPanelInfo` · `ShowRightPanelChanges` · `ShowRightPanelFiles` |
+| SSH | `ToggleSftp` · `ShowSshForwards` · `OpenSshProfiles` |
+| Application | `About` · `CheckForUpdates` · `OpenDocumentation` · `OpenDiscord` · `ReportIssue` · `ShowAll` · `ZoomWindow` |
+
+## Rebinding syntax
+
+```json
+{
+ "keybindings": {
+ "ResizePaneLeft": "ctrl-alt-left",
+ "ToggleSftp": "secondary-shift-u",
+ "ScmSync": "ctrl-b s"
+ }
+}
+```
+
+`secondary` means ⌘ on macOS and Ctrl elsewhere. A space
+separates the steps of a chord.
+[More →](/customization/keybindings)
diff --git a/docs/reference/privacy.mdx b/docs/reference/privacy.mdx
new file mode 100644
index 00000000..9b405502
--- /dev/null
+++ b/docs/reference/privacy.mdx
@@ -0,0 +1,60 @@
+---
+title: "Privacy and permissions"
+description: "What macOS asks you, why, and what tty7 itself holds."
+---
+
+## Why macOS asks tty7 for permission
+
+Panes are forked from tty7's own bundled executable, so when a program you run
+asks macOS for a protected resource, macOS attributes the request to **tty7.app**
+— not to the program.
+
+If tty7 declared no usage strings, that request would be **denied outright with
+no prompt at all**, and the program would look broken for no visible reason.
+
+So tty7 declares the matching usage strings, and you get the normal one-time
+prompt:
+
+
+
+ Camera · microphone · Bluetooth · location · motion
+
+
+ Contacts · calendars · reminders · photo library
+
+
+ Local network · Apple Events · speech recognition · system administration
+
+
+
+
+ **Declaring a usage string is not the same as holding the permission.**
+ tty7.app itself is granted none of these. Every prompt you see belongs to
+ whatever you ran in the pane, and you can revoke it under **System Settings →
+ Privacy & Security**.
+
+
+### Full Disk Access
+
+Apple defines no usage-string key for it. Reaching `~/Library/Mail`,
+`~/Library/Messages`, `~/Library/Safari`, or `~/Library/Containers` needs a
+manual grant in **System Settings → Privacy & Security → Full Disk Access**.
+
+## What leaves your machine
+
+| | |
+|---|---|
+| **Update checks** | A request to the GitHub releases API every six hours, plus the download when you accept one. Turn it off with `check_for_updates: false`. |
+| **Remote server installs** | Downloading a `tty7-server` binary for a machine you connected to — or, for WSL, copying the one already bundled with your install. |
+| **Everything else** | Nothing. There is no telemetry, no analytics, and no account. |
+
+Both of the above honour `http_proxy`. [Updates →](/reference/updates#proxies)
+
+## What is stored, and where
+
+| | |
+|---|---|
+| Settings, themes, window state | `~/.config/tty7/` (`%APPDATA%\tty7\` on Windows) |
+| SSH passwords and key passphrases | The **OS keychain** — never `config.json`, never plain text on disk |
+| Pane scrollback tails | `/scrollback/*.bin`, mode `0600` on Unix and behind the config directory's ACL on Windows. 256 KiB per pane, kept only until something can no longer ask for it: closing a pane deletes its file at once, a restore consumes it, and a periodic pass collects the rest. |
+| Shell history | Your shell's own file, exactly as before — unless you turned on per-pane history, which merges back into it. |
diff --git a/docs/reference/shell-integration.mdx b/docs/reference/shell-integration.mdx
new file mode 100644
index 00000000..9e732d29
--- /dev/null
+++ b/docs/reference/shell-integration.mdx
@@ -0,0 +1,73 @@
+---
+title: "Shell integration"
+description: "What tty7 injects into your shell, and what it buys you."
+---
+
+A terminal that only sees bytes cannot tell a prompt from output, or a finished
+command from a hung one. tty7's shell integration closes that gap: the shell
+reports where prompts begin, what was submitted, what it exited with, and where
+it is.
+
+**You do not install it.** It is injected when the pane's shell starts, and
+removes itself from the equation if you run the same shell elsewhere.
+
+## Which shells
+
+| Shell | How it is injected |
+|---|---|
+| **zsh** | A throwaway `ZDOTDIR` whose files source yours first, then tty7's. Your `TTY7_USER_ZDOTDIR` is preserved. |
+| **bash** | An rcfile that sources your own first. |
+| **fish** | A `-C` init command. |
+| **PowerShell** | An encoded init command that wraps your existing `prompt` function and PSReadLine's line reader. |
+| **WSL** *(Windows)* | The distro's shell is bootstrapped with the same scripts. |
+| **Remote panes** | The same three POSIX shells, bootstrapped over the SSH connection. Toggle per profile with **Settings → SSH → Session → Shell integration**. |
+
+`TTY7_SHELL_INTEGRATION` is set once it is active, and guards against a second
+injection when shells nest.
+
+
+ A shell launched with custom arguments is left alone for bash and PowerShell,
+ because tty7's injection would conflict with the flags you chose.
+
+
+## What it reports
+
+| Signal | Sequence | Used for |
+|---|---|---|
+| Prompt begins / input begins | `OSC 133;A`, `133;B` | The [prompt layer](/terminal/prompt): suggestions, completion, multi-line editing |
+| Command submitted | `OSC 133;C` | Knowing a command is running; agent detection on Windows, where ConPTY exposes no foreground process group |
+| Command finished, with exit code | `OSC 133;D` | The "finished after 42s" notification, failure marks in [history](/terminal/history) |
+| Working directory | `OSC 7` | New tabs and splits opening in the right place, the sidebar's repo grouping, the git branch readout |
+| Editing mode (vi / emacs) | `OSC 133;V` | Matching tty7's key handling to your shell's mode |
+| Window title | `OSC 0` | Tab labels |
+
+## What turns off without it
+
+Run a shell tty7 does not integrate with, and everything below still works —
+it just falls back to less precise sources:
+
+- Ghost suggestions, the completion menu, and ⌃ R's fuzzy history
+- "Command finished" notifications and the failure marks in history search
+- Exact working-directory tracking (tty7 falls back to inspecting the process)
+
+Panes, splits, scrollback, search, SSH, and the CLI are unaffected.
+
+## Per-pane history
+
+When `per_pane_history` is on, the integration is also what makes it work. It
+runs *after* your own rc file — which is the only reason it can: `$HISTFILE` is
+yours to set, wherever you like, and nothing outside the shell knew where it
+pointed until then.
+
+The sequence is: seed the pane's private file from your real history so it does
+not start blank, record how much was seeded, repoint `$HISTFILE`, and merge
+everything past that mark back when the pane closes.
+
+[More about history →](/terminal/history#one-history-or-one-per-pane)
+
+## Remote shells
+
+For a remote workspace or an SSH pane, the same scripts are sent over the
+connection at login, so a remote pane reports its cwd, exit codes, and prompt
+marks exactly like a local one. Turn it off for a particular host under that
+profile's **Advanced → Session**.
diff --git a/docs/reference/troubleshooting.mdx b/docs/reference/troubleshooting.mdx
new file mode 100644
index 00000000..e06c3746
--- /dev/null
+++ b/docs/reference/troubleshooting.mdx
@@ -0,0 +1,137 @@
+---
+title: "Troubleshooting"
+description: "The things that go wrong, and what they actually mean."
+---
+
+## Start here
+
+```bash
+tty7 doctor
+```
+
+One table: whether the server is reachable, whether its wire dialect matches
+your binary, the three environment variables, pid/uptime/panes, and how many
+machine links exist. Most of what follows is a specific answer this gives you.
+
+## `tty7: command not found`
+
+The CLI is put on PATH the first time the app launches. If it is missing:
+
+- Check **Settings → Agents → Install the tty7 command on PATH** is on.
+- On Unix it symlinks into whichever of `/opt/homebrew/bin`, `/usr/local/bin`,
+ `~/.local/bin`, `~/bin`, `~/.cargo/bin` your PATH already covers — if none of
+ those are on your PATH, add one.
+- On Windows the install directory is appended to your user PATH, which needs a
+ new shell to take effect.
+- A `tty7` you installed yourself is never replaced, so an old one earlier in
+ PATH will win.
+
+Inside a tty7 pane it works regardless, since panes inherit the app's
+environment.
+
+## The server is unreachable
+
+`tty7 doctor` says so, and the GUI cannot open panes.
+
+Start it with `tty7 server start`. If you are an agent or a script,
+**do not** — tell the user instead. Starting a server they did not ask for
+changes what their GUI attaches to.
+
+For logs:
+
+```bash
+TTY7_LOG=info # must be set before the server starts
+tty7 server logs
+```
+
+## Panes came back empty
+
+A crash, a `kill -9`, or a reboot takes the shells with it — that part is
+unavoidable. The *screens* should come back: tty7 keeps a capped tail of each
+pane's output (256 KiB) and hands it to the pane that reopens on that id.
+
+It is consumed once. If a pane was restored, then closed, then reopened, the
+second time there is nothing left to restore — that is by design, not a bug.
+
+## "The background server is still running <build>"
+
+tty7 updated in place, so the app is new and your panes are still served by the
+previous build. Restarting the server picks up the new one and **ends every
+process in every pane**. There is no hurry — do it when your panes are idle.
+[Updates →](/reference/updates)
+
+## A remote machine will not connect
+
+| Message | What it means |
+|---|---|
+| *"running an old tty7 server that this copy cannot talk to"* | The server there predates your client's protocol. Let tty7 update it — this ends every session on that machine. |
+| *"running a newer tty7 server than this copy"* | Update tty7 here instead, or replace the server there. |
+| *"answered, but not as a tty7 server"* | Something else is listening, or the binary is not what tty7 expects. |
+| *"tty7 no longer has a way to reach <machine>"* | The SSH link dropped. Reconnect from the switcher. |
+
+`tty7 -m ` never dials a fresh connection by design — it uses a link
+the local server already holds. Connect from the GUI first.
+
+## ⇥ or ⌃ R is not doing what I expect
+
+Both are switches, and turning one off hands the key straight back to your
+shell:
+
+- **Settings → Input → Prompt → Tab completion**
+- **Settings → Input → Prompt → History search**
+
+If they do nothing at all in a particular pane, the shell there probably has no
+[shell integration](/reference/shell-integration) — nushell, elvish, xonsh and
+friends run fine but do not get the prompt layer.
+
+## ⌥ B types `∫` instead of moving a word
+
+That is macOS's default. Turn on **Settings → Input → Keyboard → Option (⌥) acts
+as Meta**.
+
+## CJK characters have a gap on the right
+
+Your CJK fallback advances 1.0em while the primary face advances 0.60205em, so
+the glyph does not fill its two-column slot. Install
+[Maple Mono NF CN](https://github.com/subframe7536/maple-font) — it is already
+first in the fallback chain and fits Hack exactly — or change the primary face.
+[The full explanation →](/customization/fonts#cjk-and-the-two-column-grid)
+
+## A theme in my themes folder is not showing up
+
+Settings lists it under **Not loaded from the themes folder**, with the reason.
+Usually a missing required key: `background`, `foreground`, `accent`, and `ansi`
+(with eight `normal` and eight `bright` entries) are all mandatory.
+
+## My `config.json` edits did nothing
+
+If the file cannot be parsed, tty7 starts on defaults and keeps your original at
+`config.json.corrupt` — check for that file. Otherwise:
+
+- An out-of-range number is **clamped**, not applied literally.
+- An unrecognised enum value falls back to the default with a log line.
+- `scrollback_limit` applies to **new** panes only.
+- An unknown action name in `keybindings` is skipped with a warning.
+
+## Selecting text inside vim / less selects the app's own thing
+
+Hold ⇧ while dragging to keep the gesture local, or turn off
+**Settings → Terminal → Mouse → Report mouse to apps**.
+
+## `tty7 capture … | head -1` printed a Rust panic
+
+An old build's behaviour when the reader hangs up. The data you asked for still
+arrived. On such a build, redirect to a file and slice the file instead of
+piping into `head`. Current builds exit `141` on Unix, which is exactly what
+`cat` does.
+
+## Still stuck
+
+
+
+ Ask — someone has probably hit it.
+
+
+ Include `tty7 doctor` output and your platform.
+
+
diff --git a/docs/reference/updates.mdx b/docs/reference/updates.mdx
new file mode 100644
index 00000000..fc877fda
--- /dev/null
+++ b/docs/reference/updates.mdx
@@ -0,0 +1,97 @@
+---
+title: "Updates"
+description: "How tty7 updates itself, and what the two channels mean."
+---
+
+**Settings → About** is where everything lives: the version you are on, the
+channel you follow, and the button that installs what is waiting.
+
+## How it works
+
+
+
+ tty7 checks at launch and every six hours after that. Turn it off entirely
+ with `check_for_updates: false`, or check on demand with **Check now**.
+
+
+ A found release is fetched and verified before you are asked to do anything
+ — which turns "spend five minutes downloading" into "press restart".
+ Nothing is ever *installed* without an explicit choice; the staged package
+ waits in Settings.
+
+ Turn this off on a metered connection: the packages run 25–30 MB.
+ (`auto_download_updates: false`)
+
+
+ A dedicated `tty7-updater` helper verifies the release checksum, the bundle
+ version, and — on macOS — the code-signing requirement, before replacing the
+ installation. If the relaunch fails, it puts the previous copy back.
+
+
+
+Declining an update defers it rather than retiring it; it comes back later.
+
+## Channels
+
+**Settings → About → Update channel.**
+
+| | |
+|---|---|
+| **Stable** *(default)* | Published releases. Reads `/releases/latest`, which excludes prereleases. |
+| **Nightly** | Rebuilt from the latest code every night — newer, but not release-tested. Reads the rolling `nightly` tag. |
+
+The channel is a property of your **installation**, not something inferred from
+how version numbers sort. Neither feed can hand the other an update, so a
+Nightly is never walked back onto Stable by an update it did not ask for, and an
+installation only changes channel when you change it.
+
+Switching channels invalidates what the old feed produced: the staged package,
+the deferred prompt, and any transfer still in flight.
+
+
+ A stable release outranks every dated build of its core version, which is how
+ switching back to Stable *graduates* rather than downgrades.
+
+
+## Platform notes
+
+
+
+ The new GUI reuses a running local server when its wire protocol is
+ compatible, so your shells survive the update. An incompatible server keeps
+ its shells too and raises an explicit keep-or-restart prompt.
+
+
+ Windows cannot replace a running daemon's image, so the install path stops
+ the service first — and the dialog says so before you agree.
+
+ An all-users `C:\Program Files` install cannot be updated in place and keeps
+ the release-page fallback instead. Running Setup as the signed-in user would
+ either install a second copy beside the real one, or put a bare UAC prompt
+ in front of someone whose GUI just vanished.
+
+
+ AppImage and tarball installs are replaced by downloading the new file.
+
+
+
+## "The background server is still running <build>"
+
+If Settings tells you this, tty7 was updated in place: the app is the new build,
+but your panes are still served by the previous one. Restarting the server picks
+up the new one — **and ends every process running in your panes**, shells,
+agents, and SSH sessions alike.
+
+There is no hurry. Pick a moment when your panes are idle.
+
+## Proxies
+
+Update checks and downloads resolve a proxy from, in order:
+
+1. `http_proxy` in `config.json` — `http://127.0.0.1:7890` or
+ `socks5://127.0.0.1:1080`
+2. The platform system proxy (Windows registry, macOS `SCDynamicStore`)
+3. `HTTP_PROXY` / `HTTPS_PROXY` / `ALL_PROXY`
+
+Programs running in a pane are deliberately unaffected — they inherit their
+proxy from their own environment, as in any other terminal.
diff --git a/docs/remote/port-forwarding.mdx b/docs/remote/port-forwarding.mdx
new file mode 100644
index 00000000..b23cbc95
--- /dev/null
+++ b/docs/remote/port-forwarding.mdx
@@ -0,0 +1,61 @@
+---
+title: "Port forwarding"
+description: "Local, remote, and dynamic forwards — preconfigured or added mid-session."
+---
+
+## The three kinds
+
+| | What it does |
+|---|---|
+| **Local** (`L`) | A port on this machine reaches a service on the remote side |
+| **Remote** (`R`) | A port on the remote machine reaches a service here |
+| **Dynamic** (`D`) | A SOCKS proxy on this machine, routed through the connection |
+
+## Adding one to a profile
+
+**Settings → SSH →** a profile **→ Port forwarding → + Add rule**. Rules saved
+here open with the connection, every time.
+
+A Local or Remote rule needs a listen port and a target; a Dynamic rule needs
+only the listen port. An incomplete rule tells you so rather than being saved
+half-configured.
+
+Each rule takes an optional description — *"what it's for"* — because six months
+later `8080 → 3000` explains nothing.
+
+## Adding one mid-session
+
+*SSH: Port Forwarding* in the command palette opens the **Forwards** panel for
+the current connection. Add a rule there and it starts immediately; remove it
+and it stops. These live only as long as the session unless you save them into
+the profile.
+
+
+
+
+
+## The one-click shortcut
+
+⌘-clicking a `localhost:PORT` link inside an SSH pane can open a
+temporary forward for exactly that port and then open the browser — turn on
+**Settings → Input → Links → Forward SSH loopback links**.
+
+That is the right tool for "let me look at this dev server once". For something
+you use every day, put it in the profile.
+
+## Jump hosts and proxies
+
+Multi-hop connections are configured per profile:
+
+- **Jump host** — point at another saved profile, or use a `ProxyJump` chain
+- **ProxyCommand** — an arbitrary transport command, with `%h`, `%p`, `%r`
+ substituted
+- **SOCKS5 proxy** / **HTTP proxy** — `host:port`, under **Advanced → Proxies**
+
+
+ These proxy settings are for reaching the SSH server. tty7's *own* network
+ traffic — update checks, release downloads, remote-server installs — uses
+ `http_proxy` in `config.json`, the system proxy, or the `HTTP_PROXY` family.
+ Programs running in a pane are unaffected either way; they inherit their proxy
+ from their own environment, as in any terminal.
+
diff --git a/docs/remote/sftp.mdx b/docs/remote/sftp.mdx
new file mode 100644
index 00000000..b7963868
--- /dev/null
+++ b/docs/remote/sftp.mdx
@@ -0,0 +1,48 @@
+---
+title: "SFTP"
+description: "A file browser for the machine on the other end of the connection."
+---
+
+While a pane is in an SSH session, *SSH: Remote Files* (command palette) slides
+a file panel in over it. It is a real SFTP client on the same connection — no
+second login, no second password.
+
+
+
+
+
+## Browsing
+
+The panel opens on the remote home directory. **Go to Shell Directory** in the
+overflow menu jumps it to wherever the pane's shell currently is, which is
+usually where you actually want to be.
+
+Right-click a row for **Open**, **Follow Symlink**, **Rename**, **chmod…**, and
+delete. The overflow menu adds **New Folder**, **New File**, **Upload…**, and
+**Refresh**.
+
+## Transferring
+
+| Direction | How |
+|---|---|
+| Download | Drag a file out of the panel into Finder or Explorer |
+| Upload | **Upload…**, or drag files into the panel |
+
+Uploads are written under a temporary name and renamed into place at the end, so
+a half-finished file never looks like a finished one. A name that is already
+taken asks before replacing.
+
+**Transfer History** in the overflow menu shows every transfer with its
+progress, and lets you cancel one in flight. The panel header summarises what is
+happening — *"2 transferring · 64%"*.
+
+## Permissions
+
+**chmod…** takes an octal mode (`755`, `600`). The current mode is shown in the
+row's editor before you change it.
+
+
+ For files in a [remote workspace](/remote/workspaces) you can often skip SFTP
+ entirely — the Files panel reads and writes across the link directly, and the
+ built-in editor saves back to the remote machine.
+
diff --git a/docs/remote/ssh.mdx b/docs/remote/ssh.mdx
new file mode 100644
index 00000000..ed44a8ab
--- /dev/null
+++ b/docs/remote/ssh.mdx
@@ -0,0 +1,115 @@
+---
+title: "SSH"
+description: "A native Rust SSH stack: quick connects, saved profiles, keychain credentials, jump hosts."
+---
+
+tty7 speaks SSH itself, over [russh](https://github.com/Eugeny/russh). It never
+shells out to the `ssh` binary, and there is no compatibility mode that does.
+
+That is what makes the rest possible: credentials in the OS keychain,
+[SFTP](/remote/sftp) in a side panel, [port forwards](/remote/port-forwarding)
+you can add mid-session, and authentication prompts drawn as sheets in the pane
+instead of a password echoing into your shell.
+
+
+
+
+
+## Four ways to connect
+
+
+
+ Open the palette (⌘ P) and type an address. IPv6 works with
+ brackets.
+
+ ```
+ me@devbox
+ me@devbox:2222
+ me@[2001:db8::1]:22
+ ```
+
+
+
+ Profiles live in **Settings → SSH → Hosts**. Start typing the name in the
+ palette, or open the *SSH: Manage Profiles…* command.
+
+
+
+ Type an alias you already have and tty7 resolves it natively — common fields,
+ best effort — then connects over russh. **Settings → SSH → Import from
+ ~/.ssh/config** turns aliases into real profiles.
+
+
+ `Match`, `canonicalize*`, and GSSAPI directives are not supported, and
+ there is no fallback to the system `ssh` when one appears.
+
+
+
+
+ The same connection can host whole workspaces on the far machine rather than
+ a single shell. [Remote workspaces →](/remote/workspaces)
+
+
+
+## Profiles
+
+**Settings → SSH → Hosts** holds the full connection config. The basics:
+
+| Field | |
+|---|---|
+| **Name** | A label for this connection |
+| **Host** | Hostname or IP |
+| **User** | Login user — blank resolves at connect time |
+| **Auth** | *Auto* (tries every applicable method), *Password*, *Key*, *Agent*, or *2FA* |
+| **Jump host** | Another profile, or a `ProxyJump` chain |
+| **Port forwarding** | Rules opened with the connection |
+
+**Defaults** at the top of the list is inherited by every host, so a setting you
+want everywhere is set once.
+
+Passwords and key passphrases go in the **OS keychain**, never in
+`config.json` and never on disk in plain text. **Forget Password** in a
+profile's menu removes the stored one.
+
+### Advanced
+
+Behind **Advanced** on a profile, grouped:
+
+| Group | Fields |
+|---|---|
+| **Authentication** | Identity files (one path per line, `%h`/`%r` expand), agent forwarding |
+| **Proxies** | ProxyCommand (`%h`/`%p`/`%r` substituted), SOCKS5 proxy, HTTP proxy |
+| **Algorithms** | KEX algorithms, ciphers, MACs, host-key algorithms, compression |
+| **Connection** | Keepalive interval and count, connect timeout, X11 forwarding |
+| **Session** | Shell integration, login scripts, skip banner |
+
+Everything blank means "the library default", so you only fill in what you
+actually need to override.
+
+## Authentication prompts
+
+Password, key passphrase, and 2FA prompts appear as sheets inside the pane, with
+a **Remember (keychain)** option where it makes sense.
+
+## Host keys
+
+Host keys are verified against `known_hosts` by default. A first connection asks
+you to confirm the fingerprint; a **changed** key is a much louder prompt that
+makes you type `yes` to override, because that is what a changed key deserves.
+
+**Settings → SSH → Security → Verify host keys** turns verification off
+entirely. It is on for a reason.
+
+Also under Security: **Warn before closing** a live connection, off by default.
+
+## Reconnecting
+
+⌘ ⇧ R — or *SSH: Reconnect* in the palette — restarts the session in
+the current pane. Useful after a laptop sleeps or a network changes.
+
+## What is not supported
+
+- No fallback to the system `ssh` binary
+- No `Match`, `canonicalize*`, or GSSAPI directives from `~/.ssh/config`
+- Kerberos `gssapi-with-mic` is offered by the desktop app for managed
+ connections, but is not part of the `~/.ssh/config` resolution path
diff --git a/docs/remote/workspaces.mdx b/docs/remote/workspaces.mdx
new file mode 100644
index 00000000..ff24780d
--- /dev/null
+++ b/docs/remote/workspaces.mdx
@@ -0,0 +1,114 @@
+---
+title: "Remote workspaces"
+description: "Whole workspaces hosted on another machine — files, repos, panes, and git all stay over there."
+---
+
+An SSH pane runs one shell on a remote machine. A **remote workspace** goes
+further: tty7 runs a server on the far machine, and the whole workbench points
+at it. Tabs, splits, the file tree, the git panel, the diff overlay, the process
+list — all of it is the remote machine's, rendered here.
+
+Nothing is synced or copied. The repository stays where it is.
+
+
+
+
+
+## Connecting
+
+
+
+ ⌘ ⇧ O. Machines are listed alongside your local workspaces —
+ *This Computer* first, then every saved SSH profile and, on Windows, every
+ WSL distribution.
+
+
+ tty7 connects over the same SSH stack as everything else, so profiles,
+ keychain credentials, and jump hosts all apply.
+
+
+ The first connection asks:
+
+ > tty7 will write its server binary to *devbox* so this machine can host
+ > workspaces there. Nothing else on *devbox* is touched, and no sudo is
+ > used.
+
+ It shows the exact path, version, size, source, and SHA-256 before you
+ agree. Later upgrades on that machine install silently.
+
+
+ From then on the machine's workspaces are in the switcher, and a new one
+ opens like a local one.
+
+
+
+## What gets installed
+
+| | |
+|---|---|
+| **What** | A single static `tty7-server` binary |
+| **Where** | `~/.local/share/tty7/bin/tty7-server-cp` |
+| **Privileges** | None. No sudo, nothing outside your home directory |
+| **Hosts** | Linux, x86-64 or aarch64 |
+
+The binary is named after the wire dialect it speaks, so a client and a server
+that disagree never quietly half-work — tty7 installs the matching one instead.
+
+On Windows, a WSL distribution is handed the Linux server the installer already
+shipped, so a WSL workspace needs no network access at all.
+
+## Reattaching
+
+Remote workspaces are the point at which persistence pays off twice: the panes
+survive on the remote machine whether or not your laptop is awake, and you can
+reattach from a different client entirely.
+
+A strip along the top of the window says what the connection is doing —
+*connecting*, *reconnecting (attempt 3)*, *disconnected*, or *taken over by
+someone else*. Reconnection is automatic; a workspace another client has claimed
+says so by name rather than fighting over it.
+
+## Keeping the server current
+
+Two dialogs you may meet:
+
+
+
+ The machine is serving sessions from a build whose protocol this client
+ cannot speak. tty7 has already installed a matching server, but the one
+ already running is the one your sessions are on. **Update Server** replaces
+ it and **ends every session it is hosting** — including ones this window is
+ not showing. Cancel leaves the machine exactly as it is.
+
+
+ Same consequence, deliberately: every shell on that machine ends. Workspaces
+ and layouts are kept and come back with fresh shells.
+
+
+
+
+ Both of these end other people's work if the machine is shared. tty7 spells
+ out what will happen before either one runs — read it.
+
+
+## What a remote pane cannot do
+
+- **Fork an agent session.** The fork command would run against the *local*
+ agent, so tty7 does not offer it.
+- **Move very large files through the Files panel.** Drag-and-drop across the
+ link is capped at what one control frame can carry; past that the panel tells
+ you to use [SFTP](/remote/sftp).
+
+## From the CLI
+
+```bash
+tty7 machine ls # this machine plus every link the server holds
+tty7 -m devbox ls # route any command to a linked machine
+tty7 -m devbox run -- cargo test
+```
+
+`-m` matches the full link key (`me@devbox:22`) or just the host. It uses a link
+the local server *already* holds — it will not dial a fresh connection, and it
+says so rather than guessing. Connect from the switcher first.
+
+[CLI overview →](/cli/overview)
diff --git a/docs/terminal/history.mdx b/docs/terminal/history.mdx
new file mode 100644
index 00000000..4b3f1433
--- /dev/null
+++ b/docs/terminal/history.mdx
@@ -0,0 +1,57 @@
+---
+title: "History"
+description: "Fuzzy history search, and whether each pane gets its own."
+---
+
+## Fuzzy search with ⌃ R
+
+⌃ R opens a fuzzy search over what you have actually run. Type any
+fragment — the letters do not have to be adjacent — and the list narrows.
+
+Each row carries the context the plain shell version throws away:
+
+- **where** you ran it, so `npm run dev` from three repositories is three
+ distinct entries
+- **when**, as a relative time
+- **whether it failed**, from the exit code
+
+Ranking mixes frequency with recency, and commands you ran in the *current*
+directory are pushed up — the thing you want is usually the thing you last did
+here.
+
+
+
+
+
+⏎ puts the command on the prompt. Esc closes without
+touching it.
+
+### Handing ⌃ R back
+
+If you already have an fzf, percol, atuin or McFly binding you like, turn off
+**Settings → Input → Prompt → History search** (`history_search: false`).
+⌃ R then goes to the shell, and whatever you bound there keeps
+working.
+
+## Where the history comes from
+
+Your existing shell history file, as-is. Nothing is imported or converted, there
+is no separate store to warm up, and a history written outside tty7 shows up
+immediately.
+
+## One history, or one per pane
+
+By default every pane shares your shell's history file, which is what a terminal
+has always done: a command typed in one pane is available in the next.
+
+**Settings → Terminal → Give each pane its own shell history**
+(`per_pane_history: true`) changes that. Each pane gets a private history file:
+
+- **seeded** from your real history when the pane opens, so it is not blank
+- **merged back** into your real history when the pane closes, so nothing typed
+ is lost
+
+Useful when several agents or several tasks are running side by side and you do
+not want their commands interleaved in your ↑ key. Off by default,
+because someone who has not asked for it would experience the change as their
+history mysteriously forgetting the other window.
diff --git a/docs/terminal/links.mdx b/docs/terminal/links.mdx
new file mode 100644
index 00000000..0b8c3d34
--- /dev/null
+++ b/docs/terminal/links.mdx
@@ -0,0 +1,51 @@
+---
+title: "Links"
+description: "Opening URLs, files, and localhost ports straight out of the terminal."
+---
+
+Hold ⌘ (Ctrl on Windows and Linux) and links under the
+pointer underline; click to open one.
+
+## URLs
+
+Anything that looks like a URL is detected, including one the shell wrapped
+across two lines — tty7 stitches it back together before opening it.
+
+Turn detection off with **Settings → Input → Links → Detect URLs**
+(`link_url: false`).
+
+## Files
+
+A file path in the output — a compiler error, a test failure, a `grep -n` hit —
+opens in your default application for that file type.
+
+To send it somewhere specific instead, set **Settings → Input → Links → Open
+files with**. The command runs with placeholders substituted:
+
+```
+code --goto {path}:{line}:{column}
+zed {path}:{line}
+herdr edit {path} --line={line}
+```
+
+`{path}`, `{line}`, and `{column}` are filled in from the link. A flag whose
+value is not available is dropped rather than passed empty — so
+`--line={line}` simply disappears when the link had no line number. Leave the
+field blank to go back to the default application.
+
+The same setting is `link_file_command` in `config.json`.
+
+## localhost ports
+
+⌘-clicking `localhost:3000` opens it in your browser, which is only
+useful if the server is on this machine.
+
+When the pane is inside an SSH session it usually is not. Turn on **Settings →
+Input → Links → Forward SSH loopback links** (`ssh_loopback_forward: true`) and
+tty7 opens a temporary port forward through that connection first, so the link
+reaches the server on the remote machine.
+
+
+ For a forward you want to keep, set one up properly instead —
+ [port forwarding](/remote/port-forwarding).
+
diff --git a/docs/terminal/mouse-and-scrolling.mdx b/docs/terminal/mouse-and-scrolling.mdx
new file mode 100644
index 00000000..fa842da3
--- /dev/null
+++ b/docs/terminal/mouse-and-scrolling.mdx
@@ -0,0 +1,69 @@
+---
+title: "Mouse, scrolling, and the bell"
+description: "How the pointer, the wheel, and ^G behave — and how to change each."
+---
+
+## Scrolling
+
+The wheel and trackpad scroll the pane's scrollback. All of it lives under
+**Settings → Terminal → Scrolling**:
+
+| Setting | Default | What it does |
+|---|---|---|
+| **Scrollback** | 10,000 lines | How much history each pane keeps, up to 100,000. Applies to new panes. |
+| **Scroll speed** | 1.0 | A multiplier on wheel scrolling (0.1–10). |
+| **Smooth scrolling** | On | Eases each wheel notch into place over a few frames instead of jumping the whole way. Trackpads scroll continuously already and are unaffected. |
+
+⌘ plus the wheel zooms the font instead of scrolling.
+
+## The pointer
+
+Under **Settings → Terminal → Mouse**:
+
+| Setting | Default | What it does |
+|---|---|---|
+| **Focus follows mouse** | Off | Hovering a pane focuses it without a click. |
+| **Hide mouse while typing** | On | The pointer disappears as you type and returns on the next move. |
+| **Report mouse to apps** | On | Full-screen programs — vim, tmux, `htop` — get clicks and scroll events themselves. Hold ⇧ to keep a gesture local and select text instead. |
+
+
+ If selecting text inside `vim` or `less` grabs the app's own selection instead
+ of yours, hold ⇧ while you drag.
+
+
+## Font size
+
+| | |
+|---|---|
+| ⌘ + · ⌘ − | Bigger · smaller |
+| ⌘ 0 | Back to the configured size |
+| ⌘ + wheel | Zoom by scrolling over a terminal |
+
+The base size is **Settings → Appearance → Typography → Font size**, 15 px by
+default. The rest of the interface has its own size — **Interface font size**,
+16 px, adjustable from 12 to 24 — so you can scale the chrome without touching
+the terminal grid, or the other way round.
+
+## The bell
+
+**Settings → Terminal → Bell → Terminal bell** decides what `^G` does:
+
+| Mode | Behaviour |
+|---|---|
+| **Off** | Nothing |
+| **Visual** *(default)* | A brief flash |
+| **Audible** | The system sound |
+| **Both** | Flash and sound |
+
+## Command-finished notifications
+
+**Settings → Window & Tabs → Notifications** posts a desktop notification when a
+foreground command finishes:
+
+- **Notify on command finish** — *Never*, *When unfocused* (default), or
+ *Always*
+- **Notify threshold** — how long a command has to run to qualify, 10 seconds by
+ default
+
+Coding agents use the same policy for their own notifications.
+[Agent status →](/agents/status)
diff --git a/docs/terminal/prompt.mdx b/docs/terminal/prompt.mdx
new file mode 100644
index 00000000..e430c190
--- /dev/null
+++ b/docs/terminal/prompt.mdx
@@ -0,0 +1,130 @@
+---
+title: "The prompt"
+description: "Ghost suggestions, tab completion that explains itself, syntax highlighting, and real multi-line editing."
+---
+
+tty7 puts an editor at the shell prompt. Nothing to install, no plugin to source
+— the moment a supported shell starts in a pane, the prompt behaves like this.
+
+
+
+
+
+## Ghost suggestions
+
+As you type, the rest of the line is filled in from your history, greyed out
+ahead of the cursor.
+
+| | |
+|---|---|
+| → | Accept the whole suggestion |
+| Keep typing | The suggestion narrows |
+| Esc or anything that does not match | It disappears |
+
+Your existing shell history is what feeds it — there is no separate database to
+build up first, and it carries across sessions and reboots.
+
+## Tab completion, with descriptions
+
+⇥ opens a completion menu that knows what it is offering:
+
+- **Commands** from your PATH and your shell's builtins
+- **Files and directories**, with `cd`, `pushd`, `popd`, and `rmdir` offering
+ directories only
+- **Flags and subcommands** with their descriptions, for about 100 common
+ commands — `git`, `cargo`, `docker`, `kubectl`, `npm`, `brew` and the rest
+- **Values** where a flag only takes certain ones
+
+
+
+
+
+When tty7 has nothing useful to offer, the ⇥ falls through to your
+shell's own completion, so a carefully configured zsh setup is not lost.
+
+To hand ⇥ back to the shell entirely, turn off **Settings → Input →
+Prompt → Tab completion** (`tab_completion` in `config.json`).
+
+## Syntax highlighting
+
+The line you are typing is coloured as you type it: the command, its flags, its
+arguments, paths, quoted strings, operators, comments. It is a fast tokenizer,
+not a shell parser — it never changes what gets run.
+
+## Line editing
+
+The prompt behaves like a text field, because it is one:
+
+- **Click to place the caret** anywhere in the line
+- **Select with the mouse**, drag to extend
+- **Word motion** and word delete
+- **Undo**
+
+Everything readline does still works — this sits on top, it does not replace it.
+
+
+ On macOS, turn on **Settings → Input → Keyboard → Option (⌥) acts as Meta** if
+ you want ⌥ B / ⌥ F to move by word instead of typing
+ `∫` and `ƒ`.
+
+
+## Typing with an IME
+
+Pinyin, Kana, Hangul and the rest work in a pane the way they do in a text
+field: the composition is drawn in place at the cursor and only the committed
+text reaches the program.
+
+Two rules decide who gets a keystroke:
+
+- **A plain printable key goes to the IME.** A key held with ⌃,
+ ⌘, fn, or ⌥ does not — those are chords, not
+ characters.
+- **A program that asks for every key gets every key.** When something turns on
+ the kitty keyboard protocol's report-all-keys mode, the IME steps aside so the
+ program sees raw input.
+
+
+ On macOS with **Option (⌥) acts as Meta** turned on, ⌥ chords
+ bypass the IME entirely, so ⌥ B reaches your shell as meta-b
+ instead of being eaten as a dead key.
+
+
+Rendering CJK well is a separate question — see
+[fonts and the two-column grid](/customization/fonts#cjk-and-the-two-column-grid).
+
+## Multi-line commands
+
+A command that wraps, or one you deliberately break across lines, edits in
+place. The grid shifts to keep the caret visible instead of scrolling the whole
+screen away.
+
+| | |
+|---|---|
+| ⇧ ⏎ · ⌥ ⏎ | Insert a newline instead of submitting |
+| ⏎ | Submit the whole buffer, however many lines it is |
+
+The newline key is rebindable as `InsertNewline` under **Settings →
+Keybindings**.
+
+## Which shells
+
+The prompt features arrive through tty7's shell integration, which is injected
+automatically — nothing to add to your rc file — for **zsh**, **bash**,
+**fish**, **PowerShell**, and **WSL**. Other shells (nushell, elvish, xonsh, and
+the rest) run perfectly well in a pane; they simply do not get the prompt layer.
+
+The integration is also what reports the working directory, the exit code of
+each command, and where prompts begin — which is what the sidebar's branch
+readout, the "command finished" notification, and `tty7 procs` are built on.
+
+[How shell integration works →](/reference/shell-integration)
+
+## Turning it off
+
+Both prompt features are switches, and turning one off hands its key straight
+back to the shell:
+
+| Setting | Key it releases |
+|---|---|
+| **Settings → Input → Prompt → Tab completion** | ⇥ → your shell's completion |
+| **Settings → Input → Prompt → History search** | ⌃ R → your shell's reverse-i-search, or your fzf binding |
diff --git a/docs/terminal/selection-and-clipboard.mdx b/docs/terminal/selection-and-clipboard.mdx
new file mode 100644
index 00000000..30856770
--- /dev/null
+++ b/docs/terminal/selection-and-clipboard.mdx
@@ -0,0 +1,62 @@
+---
+title: "Selection and clipboard"
+description: "Smart double-click, copy on select, and the settings around them."
+---
+
+## Selecting
+
+| | |
+|---|---|
+| Drag | Select a range |
+| Double-click | Select the thing under the cursor — see below |
+| Triple-click | Select the line |
+| ⇧-click | Extend the current selection to where you clicked |
+| ⌘ A *(macOS)* | Select all — also in the right-click menu and the command palette on every platform |
+
+### Smart double-click
+
+A double-click does not just grab a word bounded by spaces. It works out what
+you are pointing at:
+
+| Under the cursor | What you get |
+|---|---|
+| A URL | The whole URL — including one the shell wrapped across two lines |
+| A file path | The whole path |
+| An email address | The whole address |
+| A bracket or quote | The matching pair, and everything between them |
+| CJK text | The word, segmented by dictionary rather than by character |
+
+Turn it off with **Settings → Input → Selection & clipboard → Smart selection**.
+With it off, double-click falls back to plain word selection using the
+`word_separators` list from `config.json` — by default:
+
+```
+,│`|:"' ()[]{}<>⇥
+```
+
+## Copying and pasting
+
+| | macOS | Windows / Linux |
+|---|---|---|
+| Copy | ⌘ C | Ctrl ⇧ C |
+| Paste | ⌘ V | Ctrl ⇧ V · ⇧ Insert |
+
+Two settings shape what lands where, both under **Settings → Input → Selection
+& clipboard**:
+
+
+
+ Selecting with the mouse copies immediately, no ⌘ C. Off by
+ default.
+
+
+ Strips trailing whitespace from every copied line — useful when copying out
+ of a TUI that pads to the pane width. Off by default.
+
+
+
+
+ Pasting multiple lines sends them as typed input, exactly as any terminal
+ does. If the shell supports bracketed paste it will treat the block as one
+ paste rather than running each line.
+
diff --git a/docs/window/command-palette.mdx b/docs/window/command-palette.mdx
new file mode 100644
index 00000000..154eb289
--- /dev/null
+++ b/docs/window/command-palette.mdx
@@ -0,0 +1,58 @@
+---
+title: "Command palette"
+description: "One key for every action in the app — including the ones with no shortcut."
+---
+
+⌘ P opens the command palette. Type to filter, ↑
+↓ to move, ⏎ to run.
+
+Every action tty7 can perform is in here, whether or not it has a keybinding —
+which makes it the fastest way to reach the ones that deliberately ship unbound,
+like pane resize and swap.
+
+
+
+
+
+## What is in it
+
+Results are grouped, and the groups are the map of the app:
+
+| Group | Examples |
+|---|---|
+| **Tabs & Panes** | New Tab · New Worktree Tab… · Split Right · Zoom Pane · Focus Pane Left · Resize Pane Up · Swap Pane Next · Reopen Closed Tab · Copy Working Directory · Fork Session |
+| **Workspaces** | New Workspace · Switch Workspace… · Rename Workspace… · Stop Workspace… · Delete Workspace… |
+| **View** | Show/Hide Left Sidebar · Show/Hide Right Panel · Show Code Panel · Tab Bar: Move to Top · Right Panel: Info / Changes / Files · Change Theme… · Enter Full Screen · Toggle Unified / Side-by-Side Diff |
+| **Git** | Commit · Stage All Changes · Unstage All · Discard All · Checkout to… · Create Branch… · Sync · Push · Pull · Fetch · Toggle Commit History |
+| **Terminal** | Clear Scrollback · Find in Terminal… · Find Next / Previous · Copy · Cut · Paste · Select All |
+| **SSH** | Add Connection… · Manage Profiles… · Reconnect · Remote Files · Port Forwarding |
+| **Agents** | Send Selection · Send Git Diff for Review · Copy Session ID |
+| **Application** | Settings… · Keyboard Shortcuts · Check for Updates… · Documentation · Join the Discord · Report an Issue… · Restart Server… |
+
+Entries that do something destructive say so under the name — *Delete
+Workspace…* is subtitled "ends its shells and forgets the layout", *Restart
+Server…* is "ends every running shell; layout is kept".
+
+## Connecting from the palette
+
+Type an SSH address and the palette offers to connect to it:
+
+```
+me@devbox
+me@devbox:2222
+[::1]:22
+```
+
+Saved profiles and `~/.ssh/config` aliases show up the same way — start typing
+the name. [SSH →](/remote/ssh)
+
+## Sending context to an agent
+
+Two palette commands hand what is in front of you to the coding agent running in
+the pane, as a ready-made prompt:
+
+- **Agent: Send Selection** — the current selection.
+- **Agent: Send Git Diff for Review** — the repository's `git diff`.
+
+If no agent is running, tty7 says so rather than typing into your shell.
+[Agents →](/agents/overview)
diff --git a/docs/window/search.mdx b/docs/window/search.mdx
new file mode 100644
index 00000000..ad4d4f81
--- /dev/null
+++ b/docs/window/search.mdx
@@ -0,0 +1,50 @@
+---
+title: "Search"
+description: "Finding text in the scrollback, and everything else the search boxes cover."
+---
+
+## In the terminal
+
+⌘ F opens the find bar over the focused pane and searches its whole
+scrollback, not just the visible screen.
+
+| | |
+|---|---|
+| ⏎ · ⌘ G | Next match |
+| ⇧ ⏎ · ⌘ ⇧ G | Previous match |
+| Esc | Close the find bar |
+
+Two toggles sit in the bar:
+
+- **Match case** — off by default, so a lowercase query matches anything.
+- **Use regular expression** — the query becomes a regex. An invalid pattern is
+ shown as an error rather than silently matching nothing.
+
+Matches are highlighted in place and the view scrolls to each one as you step
+through. On Windows and Linux the shortcuts are Ctrl ⇧ F to open,
+F3 and ⇧ F3 to step.
+
+
+
+
+
+
+ How much there is to search is **Settings → Terminal → Scrolling →
+ Scrollback** — 10,000 lines per pane by default, up to 100,000. The change
+ applies to new panes.
+
+
+## Everywhere else
+
+tty7 leans on the same pattern in a lot of places. All of them are
+type-to-filter, no button to press:
+
+| Where | What it searches |
+|---|---|
+| ⌘ P | Commands — and SSH addresses you type in full |
+| ⌘ ⇧ O | Workspaces, tabs, and machines |
+| ⌃ R | Your shell history, fuzzily — [see History](/terminal/history) |
+| Settings search box | Every setting, by name and by keyword |
+| Files panel | Files under the workspace root |
+| Theme picker | Built-in and custom themes |
+| Font picker | Fonts installed on your system |
diff --git a/docs/window/side-panel.mdx b/docs/window/side-panel.mdx
new file mode 100644
index 00000000..11bacee7
--- /dev/null
+++ b/docs/window/side-panel.mdx
@@ -0,0 +1,66 @@
+---
+title: "The side panel"
+description: "Info, Source Control, and Files — plus the built-in editor."
+---
+
+⌘ J opens a panel on the right of the window with three tabs. It is
+hidden by default; whichever tab you leave it on is where it opens next time.
+
+
+
+
+
+## Info
+
+Everything tty7 knows about the focused pane, in one column:
+
+| Section | What it shows |
+|---|---|
+| **Session** | working directory, shell, SSH connection, git branch, `+N −M` changes, and the coding agent with its status (idle / working / waiting / done) |
+| **Processes** | the process tree inside the pane, with the foreground process marked |
+| **Ports** | every port those processes are listening on |
+
+The working directory row has **Reveal in Finder** / **Open Folder** beside it.
+The ports section is the quickest answer to "what is this pane serving, and
+where" — the same data `tty7 procs` prints.
+
+## Source Control
+
+The git panel for the focused pane's repository: staged changes, unstaged
+changes, untracked files, and merge conflicts, each in its own group. Write a
+message, commit, and push without leaving the window.
+
+[Source control →](/git/source-control)
+
+## Files
+
+A file tree rooted at the pane's working directory, with git status decorations
+on every row and a search box at the top.
+
+- **Click a file** to open it in the built-in editor.
+- **Drag a file out** to Finder or Explorer to copy it there.
+- **Drag files in** from the desktop to copy them into the folder under the
+ cursor. A folder row takes them itself, a file row stands in for the folder
+ holding it, and the empty space below the tree means the top of it. Folders
+ come in whole, the executable bit survives, and a name that is already taken
+ is asked about rather than replaced.
+
+Both directions work over a [remote workspace](/remote/workspaces) too, reading
+on one machine and writing on the other, up to the size one control frame can
+carry — past that the panel tells you to use [SFTP](/remote/sftp).
+
+## The editor
+
+⌘ ⇧ E toggles the code panel; clicking a file in the Files tab opens
+it there. It is a real editor — syntax highlighting, line and column readout,
+wrap toggle, and a Markdown preview — meant for the edit you would otherwise
+have opened `vim` for.
+
+| | |
+|---|---|
+| ⌘ S | Save |
+| Esc | Back to the terminal |
+
+Files are watched on disk: a change underneath you is picked up, and closing
+with unsaved edits asks before discarding them. Files over 4 MB and anything
+that looks binary are refused with a note rather than opened badly.
diff --git a/docs/window/sidebar.mdx b/docs/window/sidebar.mdx
new file mode 100644
index 00000000..9b49e427
--- /dev/null
+++ b/docs/window/sidebar.mdx
@@ -0,0 +1,76 @@
+---
+title: "The sidebar"
+description: "Tabs grouped by repository, with branch, diff counts, and agent status on every row."
+---
+
+The left sidebar is tty7's tab bar, and it is the default because a vertical row
+has room for things a horizontal chip does not: the repository a tab belongs to,
+the branch it is on, how much has changed there, and what a coding agent in it
+is doing.
+
+⌘ B shows and hides it. Drag its right edge to resize.
+
+
+
+
+
+## Grouped by repository
+
+Rows sit under a header per git repository, with everything else collected in a
+trailing **Scratch** section. The grouping follows the tab's working directory,
+not its history — switching branches or `cd`-ing around inside a repository
+never moves a row out from under its header.
+
+**Settings → Window & Tabs → Sidebar grouping** switches between *By repo* (the
+default) and *Flat*.
+
+## What a row tells you
+
+
+
+ A brand avatar when a coding agent is in the tab, plus a status dot —
+ blue for working, amber for needs-your-input, green for done.
+
+
+ The pane's git branch, refreshed on `cd` and whenever a command finishes.
+
+
+ The working-tree diff as `+N −M`. Click the counts to open the
+ [diff overlay](/git/diffs).
+
+
+ An unread marker on tabs that produced output while you were elsewhere.
+ *Mark as Unread* is in the right-click menu.
+
+
+
+If you would rather the counts not be clickable, turn off **Settings → Window &
+Tabs → Open diff preview from sidebar counts**. The branch and the numbers stay;
+they simply stop opening the overlay.
+
+## Rearranging
+
+Drag a row to reorder it, or drag a whole group header to move the group.
+Dropping a row into another group moves the tab there.
+
+## Naming
+
+Almost no tab has a name of its own, so the label falls back through the best
+evidence available, in order:
+
+1. a name you set (right-click → **Rename Tab…**)
+2. the coding agent running in the tab — "Claude Code"
+3. the last segment of the working directory
+4. the foreground process
+
+That order is also what `tty7 tab ls` reports as `label`, with `name` left
+literal so a script can tell a real name from a stand-in.
+
+## The switcher
+
+⌘ ⇧ O opens the workspace switcher: every workspace on every machine
+you are connected to on the left, that workspace's tabs on the right. Type to
+filter both, ⇥ to cross into the tab column, ⏎ to open.
+
+From here you can also rename a workspace, open one in a new window, stop one,
+or connect to a machine you have a profile for.
diff --git a/docs/window/tabs-and-splits.mdx b/docs/window/tabs-and-splits.mdx
new file mode 100644
index 00000000..423fe05d
--- /dev/null
+++ b/docs/window/tabs-and-splits.mdx
@@ -0,0 +1,99 @@
+---
+title: "Tabs and splits"
+description: "Opening, arranging, and rearranging the panes in a tab."
+---
+
+
+ Keys are written in macOS notation. On Windows and Linux, read ⌘ as
+ Ctrl ⇧ for most window actions — the exact chords are on the
+ [keyboard shortcuts](/reference/keyboard-shortcuts) page.
+
+
+## Tabs
+
+| | |
+|---|---|
+| ⌘ T | New tab |
+| ⌘ W | Close the tab (or the focused pane, if the tab has more than one) |
+| ⌘ ⇧ T | Reopen the tab you just closed |
+| ⌘ 1 … ⌘ 9 | Jump to tab 1–9 |
+| ⌃ ⇥ · ⌃ ⇧ ⇥ | Hold to walk the switcher forwards or backwards; it commits when you let go |
+
+A new tab always opens in the current pane's directory. Where it lands in the
+list is **Settings → Window & Tabs → New tab position** — *After current* by
+default, or *At end*.
+
+Right-click a tab for the rest: rename, close others, close to the right, copy
+the working directory, mark unread, and — when a coding agent is running there —
+fork its session.
+
+## Splits
+
+| | |
+|---|---|
+| ⌘ D | Split right |
+| ⌘ ⇧ D | Split down |
+| ⌘ ] · ⌘ [ | Next pane · previous pane |
+| ⌘ ⌥ ← → ↑ ↓ | Focus the pane in that direction |
+| ⌘ ⇧ ⏎ | Zoom the focused pane to fill the tab |
+| ⌘ ⏎ | Fullscreen the window |
+
+A split inherits the current pane's working directory, so splitting inside a
+repository keeps you in the repository.
+
+Resizing and swapping panes have no default keys — bind them under **Settings →
+Keybindings**, or run them from the command palette (*Resize Pane Left*, *Swap
+Pane Next*, and friends).
+
+Inactive panes are dimmed slightly so the focused one is obvious. Turn that off
+with **Settings → Appearance → Dim inactive panes**.
+
+## Rearranging by dragging
+
+Hover a pane and a small grip appears along its top edge. Drag it to move that
+pane somewhere else in the tab.
+
+
+
+
+
+Where you drop it decides what happens:
+
+
+
+ The pane goes in **beside** that one. If it is facing a neighbour in the
+ same row or column, it joins that row and takes an equal share of it. Only
+ when it faces across the layout — where there is no row to join — does it
+ split that pane in half.
+
+
+ The two panes **trade places**.
+
+
+ The band beyond the outermost edge — the side facing the window rather than
+ another pane — makes the dragged pane a full-width or full-height band
+ beside everything else, sized to an even share of what that side already
+ holds. A pane in the middle of a 2×2 becomes a full-height *third* column in
+ one drag, rather than taking half the window.
+
+
+
+The landing lights up while you drag, and only ever lights up when the drop
+would actually change the layout.
+
+## Closing something that is busy
+
+Closing a pane or tab with a command still running asks first, and says what is
+running: *"cargo is still running. Closing ends it."* When a coding agent is
+mid-turn it says that instead — *"Claude Code is still working. Closing ends its
+turn."*
+
+## Where the tabs live
+
+**Settings → Window & Tabs → Tab bar position** puts tabs in a vertical sidebar
+on the left (the default) or a horizontal strip on top. The sidebar has room for
+things a strip does not — git branch, diff counts, agent status — so most of
+[its own page](/window/sidebar) is about that.
+
+⌘ B toggles the sidebar; ⌘ J toggles the
+[side panel](/window/side-panel) on the right.