Commit Graph
16 Commits
Author SHA1 Message Date
l0ng-ai 03c3081a6e feat(ports): detect and forward what a remote pane is serving
A remote workspace's ports were never listed. The pane lives in the
peer's registry and QueryProcs asks this machine's daemon, which has
never heard of it, so the answer was an empty list — indistinguishable
on screen from a pane serving nothing. Add a control request so the peer
answers instead, gated on a feature so an older server says "I cannot
tell you" rather than "nothing is listening".

With the ports visible, the forward becomes something the user should
not have to think about: a port opens on a click, and a new one is
forwarded unasked, at the same number where that number is free here.
The watch runs with the panel shut, which is when a port appearing is
most worth saying something about.

Ports and Forwards were two sections that never mentioned each other; a
row is now a port, and the forward is where that row says it comes out.
Adding one by hand asks for one number instead of five fields, with the
rest of the ssh -L grammar one disclosure away.

Claude-Session: https://claude.ai/code/session_01TPXrptp2rCGKjXaz4xE3Lq
2026-09-08 21:01:50 +08:00
l0ng-ai 92c1ae9f26 feat(ui): make opening a new window a bindable action (#793)
Refs #710; does not close it (the request was a jump list opening a chosen existing workspace).

Registers NewWindow globally as well as on the render root, since with the tray icon on (the default) closing the last window retires to the tray and leaves no window to dispatch it.
2026-09-07 23:45:17 +08:00
bytehelloandbytehello 42424c7737 feat(ui): add a bindable Close Window action (#773) (#778)
26.9.0's tray-retire model made closing the last window the "keep the daemon,
drop the UI process weight" gesture, but that path was reachable only from the
OS red close button: no action, no palette entry, nothing to bind. `⌘W` closes
a pane or tab, `⌘H` hides the window but keeps it in memory, and `⌘Q` stops the
server. There was nothing that closed the window and left the shells running.

`CloseWindow` is that action, with no default key — the slot is left free.
The logic that decided what a window close means (detach the workspace, and
on the last window retire to the tray if an icon is actually up, otherwise
quit) moves out of `on_window_should_close` into `prepare_window_close`, so
the button and the action share one decision instead of two that can drift.
Notably not routed through `close_window_for`: that one recycles the last
window onto a fresh workspace, which is what deleting a workspace wants and
not what closing a window wants.

It reaches the command palette, the Keybindings UI, and the reference table
of actions with no default key. In the palette it sits beside Quit, because
that pair is the whole point of the action: both end the window in front of
you and only one takes your shells with it. Their subtitles now say which —
including Quit's, which had been promising "shells keep running" while
calling `daemon::spawn::stop()`.

Co-authored-by: bytehello <bytehello@users.noreply.github.com>

https://claude.ai/code/session_01LKMZVh6mUBxXAn6v7P6JC6
2026-09-07 22:29:08 +08:00
ayamirandl0ng-ai e231b16fb3 feat(ssh): allow remote image clipboard writes (#766)
* feat(ssh): allow remote image clipboard writes

* fix(ssh): keep a profile's clipboard grant across a re-attach

A native ssh pane's OSC 5522 permission is decided by the spec that
dialled the host, and the daemon is the only side that holds it. A window
reopening onto a pane that outlived it attaches by pane id, has no spec
to read, and sends `allow_remote_clipboard_write: false` — which the
daemon took as the new answer and the pane's own view took as a refusal.
Both sides then said no, so the first restart after switching the
permission on turned every copy into an `EPERM` with the switch still
reading "on".

Pin the spec's answer in the pane and route both attach and detach
through one decision point, so a pane that carries a spec keeps that
spec's answer whatever an attaching client claims, and a pane without one
— everything on a remote `tty7-server` — is exactly as permitted as its
controller says. On the client side, refuse only what the pane can see is
forbidden and leave the verdict to the daemon otherwise.

Also: release a failed transfer's buffered bytes instead of parking up to
`MAX_CLIPBOARD_BYTES` per pane until the next request, and answer the
capability probe with the permission actually in force rather than a
constant that always reads as "off".

---------

Co-authored-by: l0ng-ai <24760907+l0ng-ai@users.noreply.github.com>
2026-09-02 14:12:28 +08:00
oldhuandl0ng-ai 0b6c332618 feat(settings): add interface font family configuration in appearance typography (#761)
* feat(settings): add interface font family configuration in appearance typography

* fix(settings): let the interface font go back to the system face

Three things the new **Interface font family** row spelled once and needed
twice.

`apply_theme` only wrote `Theme.font_family` when the setting was `Some`,
and `Theme::change` never puts it back — it rewrites the field only when a
theme config names a face, and none of ours does. So picking a font worked,
and picking **Default** back saved `None`, redrew every window in the font
the user had just cleared, and only came true at the next launch: a setting
that looked like it had applied instantly and had not. The face is now
assigned in both directions, against the stock value read once before
anything overrode it.

The dropdown's first row borrowed the bold/italic label, "Default (match
primary)" — which promises the *terminal's* primary family. The interface
falls back to the system UI font instead, so the row said the chrome would
come out in Hack while the description beside it said the opposite. It gets
its own label in all three locales.

`ui_font_family` was also the one key in `config.json` that disappeared when
unset; every other optional key is written as `null`. Dropped the
`skip_serializing_if` so the file still lists it, and documented the key in
the two tables that enumerate the typography settings.

---------

Co-authored-by: l0ng-ai <24760907+l0ng-ai@users.noreply.github.com>
2026-09-02 14:07:59 +08:00
l0ng-ai 958d8b7442 feat(window): dock the code panel and the diff overlay beside the terminal (#625) (#685)
* feat(window): dock the code panel and the diff overlay beside the terminal (#625)

Opening a file covered the workspace. The terminal underneath kept
running and was neither visible nor typeable, so reading a file while an
agent talked was a toggle loop: open it, close it to read the reply, open
it again. The Files tree already docks; the two surfaces you go to *from*
it did not.

They dock now, as a flex sibling of the terminal column rather than a
narrower overlay — that distinction is the feature. `set_grid_size` is
driven by the terminal element's laid-out bounds, so a column takes width
away from the grid and the PTY reflows into what is left; a card painted
over half the workspace would have left the grid full width with half of
it hidden.

`overlay_top` stops ordering a pair and starts choosing between them: a
column has one child, and two `flex_1` siblings would split it and fight.
Fill mode keeps the old vector, the old opaque paint and the old platform
hoist untouched, so nothing about today's overlay changes for anyone who
picks it.

- Half the terminal column by default; drag the divider, double-click it
  to cycle a third / half / two thirds, or use the palette commands. Two
  thirds deliberately runs past the half-window cap the side panels obey
  — only the terminal's floor binds it.
- `DOCUMENT_MIN_W` joins the width budget: both side panels reserve it
  the way they already reserve each other, and the column is derived from
  the *live* sidebar and panel widths rather than their floors, so a
  panel someone dragged wider is width the terminal keeps.
- A window too narrow to seat both fills for that frame. The fallback is
  derived at render time and never stored, so widening re-docks on the
  next frame with nothing to undo.
- Fill or dock is per tab, on the header's context menu. Reading a long
  file over the whole window in one tab while an agent keeps half of
  another is the normal case, and one global switch made each of those
  flip the other. A tab that has not been told reads `document_layout`
  from the config, which is what a fresh tab starts as — and which the
  menu therefore does not write, since every untold tab is reading it.
- Everywhere but macOS the title bar spans the workspace, which left a
  bar's height of nothing above the column. The header is drawn into it,
  and behaves like the title bar it now sits in. With the detail panel
  closed the column reaches the window's right edge, so the header stops
  short of the trailing chrome through a width the tab strip's own
  reservation shares.
- The docked headers drop the traffic-light inset they never had to
  clear, and the diff header's branch name becomes the thing that yields
  so the view toggle and the close tile survive a column's width.

New in `config.json`: `document_ratio`, and `document_layout` for what a
fresh tab starts as. Four new actions, bindable and unbound by default.

* fix(window): hold the docked column to widths the strip and the file agree on

Three defects in the document column, each with a guard test that fails
without its fix.

The tab strip did not know a column had taken width off it. On macOS the
strip lives inside the terminal column and sizes itself to the window less
the detail panel, so a docked document left it 340 points wider than the
column it sits in and the chips ran on under the column — the same overrun
the panel's own reservation was added for. Everywhere else the strip spans
the workspace and the column's hoisted header is drawn over its trailing
end with no fill of its own, so a chip left under it showed through the
file name and stayed clickable through it. The column's width now comes off
`strip_w` on macOS and off `corner_w` elsewhere, which is where the panel's
already goes.

The divider wrote widths the file would not keep. `Config::sanitize` holds
`document_ratio` to 0.2..=0.8; the drag clamped in pixels only, so a column
pushed against either edge of a wide window was saved outside that band and
reopened somewhere else — on a 2560-point body, 232 points from where it
was dropped. The band is a pair of shared constants now and the drag clamps
to it, the way the font size and its stepper were made to agree in #550.

The palette named the config's layout rather than the tab's. Fill is per
tab, so a tab told to fill was still offered "Document: Fill Window" — a
row that named the state it was already in and did the opposite. It reads
the active tab through `ChromeState` now.

Also: `document_layout`'s doc comment still described the global switch an
earlier draft had, three lines after the field became a per-tab default.
2026-08-19 18:03:51 +08:00
l0ng-ai 7bcb91d8af fix(input): give the PTY back the Ctrl chords tty7 was eating (#684)
* fix(input): give the PTY back the Ctrl chords tty7 was eating

Follow-up to #682, which handed Ctrl+V to a full-screen program but left
three neighbouring holes of the same shape: a key the terminal answers
without the keymap ever seeing it.

The C0 table was half a table. `input.rs` mapped the alphabet, `[ \ ]`
and Ctrl+2, and nothing else — so `Ctrl-^` (Ctrl+6, vim's alternate
file), `Ctrl-_` (readline's undo, typed as Ctrl+/ or Ctrl+Shift+-) and
Ctrl+3..8 produced no bytes at all. They were not mis-encoded, they were
silent: gpui filters control characters out of `key_char` on all three
backends, so the text fallback had nothing to offer either. The table is
now the VT-220 one, each digit beside the punctuation that shares its
key, because every platform hands Ctrl+Shift+6 over as `^` with the
Shift already spent. Ctrl+/ is xterm's addition rather than VT-220's and
is spelled out with the reason. The twenty-six letters fold to `& 0x1f`.

`on_key_down` swallowed plain Ctrl+1..9 off macOS with a bare `return`,
left over from when tabs lived on ctrl-digits — they have been on
Alt+1..9 for a long time, so nothing claimed those chords and the block
only deleted keys. It also sat before `keystroke_to_bytes`, so not even
the kitty protocol got through it. Gone.

Ctrl+V is now a binding. `AlternatePaste` carries `ctrl-v` off macOS in
a `Terminal && !alt_screen` context, and the pane declares `alt_screen`
whenever a full-screen program owns the grid, so the behaviour #682
settled on is unchanged — paste at a prompt, SYN inside vim — while the
keymap can finally express it, the Keybindings page lists it, and the
user gets a say: `"AlternatePaste": ""` hands Ctrl+V to the shell
everywhere, including readline's `quoted-insert`, and
`"PasteText": "ctrl-v"` pastes on every screen the way Windows Terminal
does. That cohort is real — Warp keeps Ctrl+V pasting on Windows on
purpose, as a removable binding, for exactly this reason. The hardcoded
arm in `handle_cmd_shortcut` now answers Cmd+V alone, which is macOS's
only paste chord and carries no control code to lose.

Last, the rule about control codes is one function instead of an
assertion buried in a test. `steals_a_control_code` plus a commented
`control_code_binding_allowed` back both the defaults test and a new
runtime warning, so a hand-edited config.json that takes EOF away from
every shell says so in the log. It warns rather than refuses: a chord
the user asked for by name is theirs to spend, the way the tmux preset
spends Ctrl+B. The invariant that still fails a build is that no
*default* spends one silently.

Tests: `cargo test --bin tty7-app` 1360 passed, 1 known flake
(`a_routed_auth_prompt_carries_the_machine_that_raised_it`, green on a
rerun and on a clean tree). New: the whole VT-220 table asserted byte by
byte, with Ctrl+- held out; `ctrl_6_reaches_the_pty_as_rs`,
`ctrl_v_pastes_at_a_prompt` and `ctrl_v_reaches_a_full_screen_program_as_syn`
drive the real keymap through `simulate_keystrokes` rather than calling
into the view; the keymap tests cover both escape hatches and the
context that withholds the binding. #682's two `handle_cmd_shortcut`
tests are replaced by those three, which assert the same behaviour at
the layer that now decides it; its end-to-end SYN test stands unchanged.
The gpui tests are unix-only, so CI is what runs them.

* fix(input): ask the grid, not the last frame, before Ctrl+V pastes

`AlternatePaste` carries `Terminal && !alt_screen`, but gpui matches a
keystroke against the frame it last painted, so the context outlives the
switch: a full-screen program that took the screen after that paint is
still "at a prompt" as far as the keymap is concerned, and the clipboard
lands in it. In vim's normal mode that runs as commands. The action now
re-reads the terminal mode and propagates instead, which hands the chord
to `on_key_down` and encodes it as the SYN the program is waiting for.

Also:

- the two escape-hatch assertions in
  `paste_ships_both_terminal_chords_off_macos_and_retires_together`
  built a one-entry binding table instead of the default one, so both
  passed without the hatch working — an emptied `AlternatePaste` cannot
  dispatch anything when it is the only entry in the table. They now
  apply the config line on top of the whole default table, and the
  `PasteText: ctrl-v` case checks both screens;
- the keyboard-shortcuts page claimed every other Ctrl chord reaches the
  program, which Ctrl+Tab and the Windows/Linux font-size chords do not;
- `steals_a_control_code` documents `@` and the backtick, which are in
  the set it walks but were not in the list beside it.
2026-08-19 17:38:28 +08:00
webdev 3c95995e82 fix(input): hand Ctrl+V to a full-screen program on the alternate screen (#677) (#682)
In vim or neovim on Windows and Linux, Ctrl+V pasted the clipboard where
the editor expected blockwise Visual mode. Windows Terminal (with its
ctrl+v binding removed), WezTerm and Alacritty all send the key; macOS
was never affected, since Cmd+V is the paste chord there.

Ctrl+V was not a keybinding at all. `on_key_down` hands plain Ctrl+C, V
and X to `handle_cmd_shortcut` off macOS, and of the three the "v" arm
was the only unconditional one: Ctrl+C copies with a selection and
otherwise falls through to SIGINT, Ctrl+X falls through outside the
editor, but Ctrl+V always consumed, so SYN never reached the PTY --
`input.rs` had the byte, unreachably -- and an empty clipboard turned the
key into nothing at all. #270 set the rule that off macOS ctrl-<letter>
belongs to the terminal and anything sitting on one must fall through;
Ctrl+V was the exception that had escaped it.

The arm is now contextual like its neighbours. On the alternate screen
it falls through, and `keystroke_to_bytes` sends 0x16, or the CSI u form
when the program has the kitty protocol on; off it Ctrl+V pastes exactly
as before, and Cmd+V on macOS is untouched. The alternate screen is the
gate rather than `input_active` because the editor is inactive whenever
shell integration is missing or the prompt editor is off, and gating on
that would take paste away from every such user; a program that has
switched screens is precisely the case reported. Inside such a program
paste is Ctrl+Shift+V, Shift+Insert or the right-click menu, all of
which still stage a clipboard image for an agent.

The same block did not exclude Shift, so Ctrl+Shift+C/V/X reached the
hardcoded path whenever the keymap had nothing on them -- exactly the
state rebinding Paste leaves behind, which #271 promised would retire
Ctrl+Shift+V, but it went on pasting behind the user's back. Only
unshifted chords enter the block now; the shifted ones are the keymap's
alone.

The right-click menu advertised Ctrl+C, Ctrl+X and Ctrl+V off macOS as
though they were the bindings, next to a Select All row that already
showed its hint on macOS only. The three rows take the same treatment,
which is also what the command palette does.

Three view tests pin the split -- Ctrl+V falls through on the alternate
screen while Cmd+V still pastes there, Ctrl+V pastes off it, and a key
down on the alternate screen arrives at the PTY as SYN and nothing else
-- and the keymap's paste test now asserts that no default claims ctrl-v
in the Terminal context. The shortcuts reference notes where plain
Ctrl+V pastes and where it is the program's.

Fixes #677.
2026-08-18 23:28:01 +08:00
l0ng-ai ef333bf055 feat(terminal): make the wheel-zoom modifier configurable (#676)
Cmd-scroll zoomed the font with no way to move it or switch it off, so a
thumb left on Cmd resized the terminal mid-scroll (#668). The modifier is
now a setting: the platform modifier by default, or Ctrl, Alt, or none.

Stored as the choice rather than the resolved key, so one config file
still means the same thing on a Mac and on a Linux box. Settings ->
Terminal -> Mouse carries the picker; off macOS Ctrl and the platform
modifier are the same key, so it shows one cell for them.
2026-08-18 12:16:17 +08:00
webdev 422808191d feat(sidebar): group a tab by its folder when its cwd is not a repo (#631)
`sidebar_grouping` gains a third, opt-in mode, `repo-or-directory`: group by repository home as before, and when the repo probe has landed and answered "not a repo", group under the cwd itself instead of filing every such tab under Scratch. A probe that has not run yet resolves to no decision, so a tab keeps the group it already has rather than bouncing through Scratch mid-probe. The decision lives in one `resolved_group` free function shared by the per-frame key derivation and spawn-time seeding.

The default (`repo`) and flat modes behave exactly as before, and an unknown value in an existing config still degrades to `repo`.

Knock-on: `machine_mirror::subject_path_of` names a window after its most common group, so in the new mode a window of plain shells takes its name from the most common directory rather than from the first pane's cwd.

Closes #620.
2026-08-14 15:57:36 +08:00
webdev 0346e35b40 fix(shell): stop injecting into a zsh or fish the user gave arguments to (#629)
The zsh and fish arms of `shell_integration::setup` never checked `has_custom_args`, so a shell the user launched with their own arguments was injected anyway — fish had `-C <script>` appended to its argv, zsh had its ZDOTDIR swapped. Both arms now sit behind the same gate bash, PowerShell and WSL already used, hoisted to a single early return ahead of the dispatch so a new ShellKind cannot silently reintroduce the bug.

Docs now describe what the code does: the `shell` row's own `{"program": "fish", "args": ["-l"]}` example loses integration under this rule, and the shell-integration note distinguishes user-written arguments from the ones detection supplies (Git Bash, WSL).

Part of #624; the native-input-mode half is separate.
2026-08-14 15:57:20 +08:00
l0ng-aiandl0ng-ai 72db26d15a feat(prompt): let the shell's own line editor own the prompt (#633)
Closes #624

tty7's inline editor takes the prompt the moment OSC 133 reports one, and
until now the only way to keep it off was to hide the shell's own name
from tty7 so integration never armed — which costs the prompt boundaries,
cwd and exit codes as well. Someone who binds `history-beginning-search-
backward-end` to Up in their zshrc had no way to reach it, and the local
history the editor walks instead is per-view: a command run in one pane is
not in another's list, so the shell's shared history looked broken too.

The new `prompt_editor` switch (Settings -> Input -> Prompt, on by
default) hands the line back. Off, every key at the prompt goes to the
PTY, so ZLE / readline / fish do the editing and what the user bound
behaves as written. Shell integration is untouched by it.

The gate is one line in `input_inactive_reason`, which every path that
could take the prompt from the shell already asks: keys, IME commits,
paste, Tab, the completion and reverse-search menus, the input bar. That
is what makes this a mode rather than a special case per key.

`shell_owns_prompt` learns the flag too, and that half matters more than
it looks: the gap hold and the typeahead record both exist to feed the
local editor, and `flush_typeahead` sends ^U to erase the line before
moving it there — on a line only ZLE is editing, that erases the user's
work. Ctrl-R landing on the PTY also stops raising the missing-integration
notice: the shell owning it is what was asked for.

Turning it off mid-line hands what is typed to the shell the way an
unknown chord does, so the text is still on the prompt to finish. Live
panes follow the switch, including a hand edit of config.json in another
window.

Tab completion and history search are menus tty7 opens inside that editor,
so the page greys them out and says why while it is off. Only their text
dims — a switch already draws its thumb at 35% when disabled, and dimming
the row on top of that leaves a pill with nothing visible in it. Their
stored values are left alone and come back with the editor.

Co-authored-by: l0ng-ai <24760907+l0ng-ai@users.noreply.github.com>
2026-08-14 14:46:22 +08:00
l0ng-aiandl0ng-ai d343fd8a13 feat(links): open file links in tty7, resolved on the pane's own host (#568)
* feat(links): open file links in tty7, resolved on the pane's own host

A clicked file path now opens in the built-in editor at the line and
column the link named, and the Files panel reveals it; a directory link
opens the panel on that directory. Settings -> Terminal -> Links -> Open
files with picks between the built-in editor, the OS file association
and a command, migrating anyone who had already set link_file_command.

Detection is split into a filesystem-free candidate parser and a probe
callback, so a pane whose paths live on another machine resolves them
there instead of against the local filesystem -- an absolute path used
to open this machine's copy silently. A pane running ssh typed into a
local shell can answer for neither side and no longer offers file links
at all.

Relative paths are measured from the directory the work is happening in
(the agent's, not the shell's kernel cwd) and then from the repository
around it, and a path that matches nothing under either now says so
instead of the click doing nothing.

* fix(links): keep a remote path off the local openers, and off a dead end

Review follow-ups on the file-link work.

- A file resolved on another machine now opens in the built-in editor
  whatever `link_file_open` says. Under `system` or `command` the path was
  handed to a local `open` / `code --goto`, which threw away the resolution
  just done on the pane's host and silently showed this machine's copy — the
  same bug this branch set out to fix, left live for two of the three modes.
  A directory outside every tree root says so instead of opening a local file
  manager on a path that belongs to the far side.

- `flush_link_probes` takes the host before it takes the wanted paths.
  `take_wanted` moves them into the in-flight set on the promise that a call
  is carrying them; a host that had gone away broke that promise for good and
  left those paths permanently unanswered — no underline, and a click that
  says nothing.

- `~` no longer borrows this machine's `$HOME` for a pane whose paths are
  elsewhere. A cwd outside `/home` and `/Users` used to fall back to it, so
  `~/.zshrc` on a Linux box became `/Users/me/.zshrc` and was asked about —
  and possibly answered — over there.

- An unresolved absolute or `~`-rooted path no longer claims it was looked
  for under the pane's directory. It never was: roots are only for relative
  paths.

- A pending tree reveal counts down whether or not its row was found. A row
  that never reported bounds kept the request alive for good, re-issuing a
  scroll on every render and holding the column against a hand scroll.

- The repo root comes from `GitStatusCache` when the git-status probe has
  already asked about that directory, rather than a second round trip.

Tests: the migration `link_file_open` exists for (an old config with a
command lands on Command, one without on the editor), a probe with no host
staying wanted, and `~` refusing this machine's home for another one.

* test(links): only claim a leading slash is absolute where it is

`is_rooted` asks `Path::is_absolute`, the same question `FileCandidate::paths`
asks before it decides the roots do not apply — and on Windows `/etc/hosts`
answers no to both. The predicate is consistent; the assertion was not, so it
now lives in a unix-gated test of its own next to the untouched one.

---------

Co-authored-by: l0ng-ai <24760907+l0ng-ai@users.noreply.github.com>
2026-08-12 22:48:16 +08:00
webdev 4da3868797 feat(shells): let the new-tab menu carry entries the user wrote (#534)
Closes #443
2026-08-12 10:52:35 +08:00
l0ng-ai 00e1aa8218 docs: correct claims that no longer match the code
Audited every page under docs/ against the source. Fixes for what the
code actually does:

- agents: the status vocabulary is idle/working/waiting/done, not
  running/waiting/idle; hook rows grow a separate Uninstall button; the
  Settings table labels read "Copilot CLI" and "Grok Build"; Copy Session
  ID lives in the tab's context menu, not the pane's
- cli: `pane ls --all` reports the owning workspace id, not "tty7-cli";
  document bare `tty7 [PATH]` as the GUI launcher it is instead of listing
  it as unimplemented; note `active_tab` and the `diagnostics` array; wait
  also defaults to $TTY7_PANE
- git: the branch dropdown is a plain list with no search box and no
  stash-and-switch, and checkout is not a palette command; quote the diff
  overlay's own overflow notice rather than the sidebar's
- window: the unread marker tracks a finished agent turn, not any output;
  rows cannot be dragged across groups; the sidebar and `tty7 tab ls`
  resolve labels differently; drop Toggle Commit History and Checkout to
  from the palette's Git group; ~/.ssh/config aliases are not palette
  entries
- terminal: Ctrl+R dedups by command text and shows no directory; Esc does
  not dismiss a ghost suggestion; document Cmd+Enter
- remote: GSSAPI is an ordinary Auth choice, not a managed-connection-only
  mechanism
- fonts: Maple Mono NF CN leads the chain on Windows and Linux only; list
  the real per-platform defaults
- settings paths: the three Links settings and per-pane history were filed
  under the wrong sections
2026-08-11 14:35:54 +08:00
l0ng-aiandl0ng-ai 707fd1867b docs: add a Mintlify documentation site (#478)
38 pages under docs/, written against the source rather than the README:
config keys and their clamps from core::config, default keybindings from
ui::keymap, every CLI verb and flag from tty7-cli, agent aliases and
hook/fork/resume support from core::cli_agent, and Settings paths taken
from the actual en-US strings.

docs/features.md and its zh-CN translation are retired — everything in
them now lives in a page of its own, plus the two things they carried
that nothing else did (IME input, the performance notes). README and
README.zh-CN point at docs/ instead.

Screenshots and videos are placeholders for now: docs/images/placeholder.svg
with a caption naming what each shot should be.

Co-authored-by: l0ng-ai <24760907+l0ng-ai@users.noreply.github.com>
2026-08-11 00:38:11 +08:00