mirror of
https://github.com/l0ng-ai/tty7.git
synced 2026-10-03 08:02:02 +00:00
e0308bd54fc2ce2921b1e797f034db439a0d62b6
40
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
b063ba97a0 |
refactor(settings): fold Window & Tabs into General, drop two settings (#982)
The Window & Tabs page is gone. Its three remaining tab settings (new tab position, tab bar position, auto grouping) are a Tabs group on General, below Startup & restore, so the nav has seven sections. Removed outright: - SSH tab title (`ssh_tab_title`, #726, unreleased). The sidebar already groups SSH tabs under their host, and renaming a tab pins its name. - Open diff preview from sidebar counts (`sidebar_diff_preview`, #247). The sidebar counts and the Info panel's changes row always open the diff overlay; the large-tree stall it worked around is bounded. An old config.json that still carries either key loads as before. The now-unused window settings icon goes with the page. |
||
|
|
fd2c4f7d4e |
feat(panel): Search and GitHub tabs in the right panel (#978)
* feat(panel): add Search and GitHub tabs to the right panel The right panel grows from three tabs to five. Five word labels do not fit the panel's 280px resting width, so the tab row now draws a glyph per tab and names it in a tooltip. Both new panes are placeholders here; the content search and the GitHub issues/PR browser land on top of this. * feat(panel): find in files in the right panel's Search tab The Search tab replaces its placeholder with a content search over the active tab's project -- the same roots the Files tab shows -- on the host that project lives on. Hits arrive as you type (debounced, with a generation counter so a stale answer never lands), grouped by file with a count, each line excerpted with its matches highlighted. Clicking a hit opens the built-in editor at that line and column; Enter searches again. Match-case, whole-word and regex toggles sit at the end of the field, and the tab focuses its field whenever it is brought forward. Host::search_content is new on the Host trait, implemented once in host::content_search (ignore walk + regex) and run by LocalHost directly and by tty7-server over a new SearchContent control request. The walk honours .gitignore with or without a repository, skips dot-entries, binary files and files over 1 MB, and reports a capped search as truncated. The request is gated on a new `content-search` hello feature, so a server that predates it is never sent it; the panel says the server needs updating instead of showing no results. Conformance cases cover local and the stdio server alike. * feat(panel): browse GitHub issues and pull requests in the right panel The GitHub tab follows the focused pane's repository: its root is resolved the way the Source Control tab does, its remotes are read through the Host (the tree may be on another machine), and the github.com remote is bound, upstream over origin in a fork, with a menu to pick another. The list switches between issues and pull requests, open and closed, 50 rows a page with Load more; rows carry a state glyph distinct by shape, labels (click one to filter by it) and relative times. A row opens the detail in place: title, state, author, labels, description and comments as Markdown, and for a pull request its branches, size and changed files. A file opens in the diff overlay through a new supplied-patch DiffSource, so GitHub's patch renders exactly like a local one without a git probe. Read-only, and sign-in reuses the GitHub CLI: GH_TOKEN, GITHUB_TOKEN, then `gh auth token`, found on PATH or at the Homebrew locations a Finder launch cannot see. Signed out, public repositories still work; 401, 403, 404 and rate limits are told apart and explained. Requests go out from this machine over the installer's ureq stack and proxy settings, on threads of their own, cached per repository with background revalidation. Remote images in issue text become links instead of loading, and non-web link targets are disarmed. The Info tab gains a GitHub row that opens the branch on the remote it tracks, or the repository for a branch never pushed. * docs: list ShowRightPanelGitHub with the other panel actions * feat(panel): one-line GitHub rows, a pill for the current tab - GitHub list rows are one line: state glyph, #number, title. Labels and the age of the last update appear on hover, from state rather than a group_hover display switch, which gpui cannot paint. - The current right panel tab sits on the sidebar's selected fill; ink alone could not tell five same-weight glyphs apart. - The GitHub glyph is a 1.8px outline like the other tab icons, not the filled mark. - The detail byline names both times (opened / updated) so it no longer reads as disagreeing with the list's update age. * feat(github): show screenshots pasted into issues Images GitHub hosts itself (github.com/user-attachments, a repo's /assets, *.githubusercontent.com) now render in issue and PR text, each in a paragraph of its own so the text view draws it at its size rather than at line height. Images from any other host stay links, so opening an issue still tells no third party that you read it. gpui held a null HTTP client, so no remote image could load; the app now installs the update check's reqwest client (same user agent and proxy) at launch. * fix(github): load private-repo screenshots, give inline code a neutral fill - Pasted attachments (github.com/user-attachments/assets/<uuid>) want a browser session on a private repository, which an API token is not. The detail and comment requests now ask for the full media type, and each attachment is swapped for the signed private-user-images URL the rendered body_html carries for the same uuid. - Inline code in rendered Markdown (the GitHub tab and the editor's preview) sits on a faint neutral fill instead of the theme accent, which is also the selection colour. Needs gpui-component 6af19d91 for TextViewStyle::inline_code_background. * style(panel): tidy the GitHub and Search tabs' top rows - GitHub drops its heading row on macOS. It existed only to hold the refresh tile, and no other tab has one; refresh now sits with the repository's other actions, in the repo row and a detail's header. - Search's Aa / ab / .* toggles are muted while off instead of body ink. - Search's idle note puts the folder on its own line, spelled ~/…, so the narrow column no longer breaks the path at a slash. * feat(panel): order the right panel's tabs Info, Files, Search, Changes, GitHub Info stays first as the default and the pane's overview; after it come two pairs, the project's files (Files, Search) and its version control from local to remote (Changes, GitHub), where Changes and GitHub were split by the file tabs before. The palette, the Keybindings list and the docs follow the same order. * style(panel): drop the change count from the Changes tab Beside one glyph of five, the number read as a badge on that tab alone, and the Changes tab already leads with the same count under its own heading. right_panel_tabs no longer needs the row's width, which it only measured to decide whether the count fit. * style(icons): fit the GitHub glyph to the other tab icons' size The Lucide mark filled its whole 24px box, edge to edge, where tty7's own icons keep about 3.5px clear, so at 15px it drew a size larger than the four tabs beside it. Scale it to 0.9 about the centre, and raise the stroke to 2.0 so it still renders at the others' 1.8. * style(icons): a simpler GitHub glyph Drop the Lucide mark's tail and redraw the head and legs on tty7's own grid: the same ~15px live area and 1.8 stroke as the other tab icons, no scale transform. The legs keep it reading as the Octocat; a head alone read as any cat. * test(github): find gh on PATH in the blank-variable token test The test placed gh only at /opt/homebrew/bin/gh, which gh_candidates never offers on Windows, so the Windows CI job panicked at unwrap. Put gh on a PATH directory spelled with the platform's exe name instead. * fix(github): close image and link bypasses in the issue Markdown sanitiser Checked against markdown-rs (the parser TextView uses), several inputs got past the line-based rewrite: - is_github_hosted cut the host only at `/`, so `https://evil.io?.githubusercontent.com/x.png` (and `#`, `\`, `/`) counted as GitHub-hosted and was fetched from evil.io. The host now ends at the first of `/?#\` and may hold only DNS characters. - `<img src>` values were written into `` unescaped, so a `)` in the value closed the image and opened a second one from any host. Written destinations are now percent-encoded. - `<image>` (which the HTML parser reads as `<img>`) passed as an ordinary tag and loaded its src. - A kept link target was copied without scanning; when the parser ended the link elsewhere (open title, unbalanced paren) a `` inside it came alive. Markup characters in it are now encoded. - `file:///...` and similar character references passed is_safe_target and decoded to a `file:` link. References are decoded before judging. The rewrite still cannot see every construct the way the parser does (code spans inside tag attributes, fences the parser rejects, multi-line link definitions), so the detail view now also checks the parsed tree: a block containing a non-GitHub image, an unsafe link or definition, or raw `<img>` is drawn as its plain source instead. * fix(github): hide gh's console, bound Retry-After, and reject URL authorities with ?#\ - run gh through proc::output_within with hide_console, so a Windows GUI launch does not flash a console window and stdout is drained while gh runs. - saturating_add a hostile Retry-After instead of overflowing i64. - parse_github_url no longer accepts `https://evil.io#@github.com/o/r`. * fix(search): no panic on an unbounded time budget, and read files through the size cap ContentLimits arrive off the wire on a server; Instant + u64::MAX ms panicked. A file that grew between the size check and the read was read whole; it is now read through a take() at the cap. * fix(panel): keep Load more on an empty filtered page, and drop another host's hits - /issues pages filtered to one kind can come back empty while later pages hold matches; the GitHub list said "No issues" and hid Load more. It now reads on through up to five such pages and keeps Load more offered. - While a new search runs, the previous hits stay on screen; if they came from another host, a click opened their path on the active host. They are now kept only when the host is the same. |
||
|
|
84935813d5 |
feat(terminal): the mouse wheel no longer zooms the font by default
mouse_zoom_modifier now defaults to none. The platform modifier is cmd on macOS, held for so much else that the font jumped size mid-scroll (#668). Picking a modifier in Settings brings the wheel zoom back; cmd+/cmd- are unchanged. |
||
|
|
6fcf659e04 |
feat(search): tabbed Search Everywhere with a Sessions tab to resume agent sessions (#969)
* feat(search): replace the command palette with tabbed Search Everywhere The palette was one flat list that every new kind of row had to be squeezed into: tabs and SSH hosts rode along as "Switch to Tab: …" and "SSH: …" commands, and the only way to narrow to one kind was a magic seed word. Search Everywhere splits it into sources behind one trait — All, Actions, Terminals, Hosts — each with its own empty-query layout and ranking. The All tab shows each source's top rows, ordered by best match, with a row that opens the full tab. Tab / Shift-Tab walk the tabs and keep the query. - Terminals lists every open tab of every workspace (reusing the switcher's tab rows) and jumps to it wherever it lives, plus the shells and agents. - Hosts replaces the separate "Add Connection" input: a typed address or full `ssh …` line offers to connect. - Fixes Return doing nothing after a search that found nothing, or when the search opens pre-filtered: gpui-component re-picks the row from a stale frame; the delegate now re-arms the first row. - Fixes `ssh -p 2222 me@box` being offered as a quick-connect address with user `ssh -p 2222 me`. The keymap action stays `TogglePalette` so custom bindings keep working. * feat(search): a Sessions tab to resume past agent sessions Search Everywhere gains a Sessions tab listing the Claude Code and Codex sessions on this computer, read from ~/.claude/projects and ~/.codex/sessions ($CODEX_HOME). Sessions that ran in the focused tab's directory lead; the All tab offers the last three of them before anything is typed. Return opens a new tab in the session's directory and runs the agent's resume command with its configured launch flags. - Only each transcript's head and tail are read, off the window thread, and cached by path, size and mtime: ~170ms cold for 50 sessions, under 1ms warm. The search opens on the cached list and fills in. - Titles: /rename name, then the agent's own title (ai-title, Codex's session_index.jsonl), then the first thing typed, skipping harness injections. Codex rollouts it ran for itself (subagent/internal) and sessions never asked anything are left out. - A session whose directory is gone is refused with a notice rather than resumed where the agent cannot find it. |
||
|
|
39ceb35fdd |
feat(cursor): give the shell prompt its own cursor shape (#963)
A new prompt_cursor_style setting (follow, block, bar, underline) shapes the caret while the shell waits at a prompt, whether tty7's inline editor or the shell's own line editor draws it. `follow`, the default, keeps cursor_style everywhere, so nothing changes until it is set. Any other value leaves cursor_style to the programs the shell runs: bar here with cursor_style block gives kitty and ghostty's bar at the prompt and a block inside a TUI that never sets a shape, such as Claude Code. A vi-mode prompt keeps the shell's own insert/normal shapes. Settings shows both cursor shapes as dropdowns, kept in step with resets, language switches and config edits made outside the window. Closes #958 |
||
|
|
e6009636b5 |
feat(tabs): reorder the active tab from the keyboard (MoveTabLeft / MoveTabRight) (#974)
* feat(tabs): MoveTabLeft / MoveTabRight — keyboard tab reordering The drag that reorders a tab now has a keyboard form: the active tab trades places with its neighbour in visual order, wrapping past either end. Ships unbound like the pane-swap pair; bindable from config.json and the Keybindings page, and listed in the palette and the docs. * fix(tabs): keep a keyboard tab move inside its sidebar group On a left tab bar the move stepped along the flat visual order, so a step out of a group reordered self.tabs without moving anything on screen (still saved, still synced as TabMove, and visible later on a top bar), and a wrap through another group landed the tab at its own group's far end by accident. The move now reassigns only the slots of the tab's own sidebar section, and wraps at that section's ends. --------- Co-authored-by: l0ng-ai <24760907+l0ng-ai@users.noreply.github.com> |
||
|
|
cf0e8f02e6 |
feat(sidebar): pinned groups above auto groups (#962)
* feat(sidebar): keep pinned groups on the workspace and derive the rest Replace the sidebar's hand-made groups with the model from #955: the sidebar groups tabs by repo automatically, and you pin what you want to keep. - Pinned groups (`PinnedGroup`: id, optional name, optional folder, fold) are stored on the workspace in the machine tree, in display order, and a tab points at one by `GroupId`. Everything else is an auto group worked out every frame and never stored: by repo home, or by `user@host` for an SSH pane — native or a shell that ssh'd onward — so `/home/ubuntu` on two machines no longer lands under one header. - A tab whose cwd *enters* a pinned folder joins it (deepest folder wins; a repo home equal to the folder counts, which keeps worktrees with their repo). It is edge-triggered through `EntryWatch`, so a tab dragged out while still inside the folder stays out until it leaves and comes back, and a tab restored at launch is not pulled in by where it already sits. - Groups sync as one `WorkspaceSetGroups` / `GroupsChanged`, pushed only from an edit and adopted from every pull, so a fresh window can never push an empty set over the workspace's. The machine hands tabs naming a dropped group back to auto grouping in the same mutation. Control dialect → v11. - Config: `sidebar_grouping` (three modes) and `sidebar_collapsed_groups` give way to one `sidebar_auto_grouping` toggle; folds live with the workspace. - `tty7 tab ls` reports the pinned group a tab is in (name or folder leaf; JSON carries id, name and folder). * feat(sidebar): draw pinned groups above a divider, with their own gestures The sidebar now reads as two halves: the groups you keep, in the order you put them, then a divider, then the groups it works out (Arc-style). - Pinned headers drag-reorder among themselves (their own reorder surface, so a pinned header cannot be dropped among the derived ones); the order lands on the workspace's group list, not on the tabs. - An auto header carried above the divider is pinned when let go. With nothing pinned yet the divider appears during that drag as a "Drop here to pin" zone, since a hairline at the top of the list is nothing to aim at. - A tab kept in a pinned group and dropped anywhere below the divider goes back to auto grouping; the divider lights to say so. - An empty pinned group stays, with a "+ New Tab" row that opens a tab in its folder (or where ⌘T would, for a label group) and files it there. - Folder groups carry a pin mark that unpins on click and a tooltip with the folder; auto headers show pin and "+" on hover. - Header menus: pinned — Rename, Set Folder… (local workspaces), Use Current Tab's Folder, Clear Folder, New Tab, Unpin (folder groups), Delete. Auto — Pin Group, New Tab. Nothing renames an auto group; nothing pins implicitly. * feat(sidebar): open folders as pinned groups from Finder, the file tree and the palette Every way into a pinned group the design calls for: - Drop a folder from Finder or Explorer onto the sidebar to pin it (a local workspace only — a dropped path is this machine's, and a folder group keeps a directory on the workspace's host). Files are let fall. - "Pin as Group" on a folder in the file tree, on local and remote workspaces alike, since the tree and the group are both on the workspace's host. - Palette "New Group" makes an empty label group and opens its name for typing; "Open Folder as Group…" picks a folder with the system picker, pins it and opens a tab in it. The picker browses this computer, so that one is not offered on a remote workspace. - Tab right-click "Move to Group" lists the pinned groups plus "New Group…", which files the tab in a fresh label group with its name open for typing. Pinning a folder already pinned hands back the group that keeps it rather than making a second one to split its tabs with. * fix(sidebar): let groups that arrive from elsewhere pull no tab into a folder A window draws its first frames before its copy of the workspace's groups lands, so every tab's entry watch recorded "in no folder" — and the groups landing then read as each tab walking into its folder. A restored tab, or one dragged out of its folder group, was pulled back in on every launch. Groups adopted from a pull or from another window's `GroupsChanged` now start every tab's watch over from where it is; only this window's own pin gathers the tabs inside the folder, and says so tab by tab. A tab also goes up with the group it names even when the window does not know that group yet, so a sync in that same gap cannot send every kept tab back to auto grouping. * docs(sidebar): describe pinned and auto groups, and log the change Rewrite the sidebar page's grouping section around "grouped by repo automatically; pin what you want to keep": the divider, folder and label groups, the edge-triggered join, every way to pin, and the header menus. The configuration reference swaps `sidebar_grouping` for `sidebar_auto_grouping`, the CLI reference describes the GROUP column as the pinned group, and the changelog gains an Unreleased entry (#955). * fix(sidebar): file a tab opened by the CLI in a pinned folder into it A tab that reaches a window as TabCreated — from `tty7 tab new` or another window — started its entry watch as a restored tab, so opening one inside a pinned folder left it in the auto group below. It is as new as a tab opened here, and now joins the folder like one; every window that hears of it reaches the same answer. * test(machine): build the group sets in their initializers Clippy's field_reassign_with_default on the two WorkspaceGroups the set-groups test assembles. * fix(sidebar): draw restored tabs in their auto group, and title by repo again Auto groups are not stored, so after a restart every tab sat in Ungrouped until its own repo probe came back, then jumped; before pinned groups the stored repo key put it in place on the first frame. Each tab now carries `last_auto`, the auto group it last resolved to, as a hint: stored with the tab, sent up alongside its group in `TabSetGroup` whenever the live answer moves, and used to draw the tab until the probe answers. The probe always wins and rewrites the hint, and the hint never outranks a pinned group or the folder-entry rule. Another window's hint only fills a gap, so two windows can never bounce a disagreement between them. The workspace's fallback title regained the repo majority it lost: the most common pinned folder first, then the repo most unpinned tabs were last filed under (a worktree counting toward its repo home), then a pane's cwd. * refactor: drop what the new sidebar left unused, and two clippy findings - `TerminalView::native_ssh_cwd` and its helper existed for the sidebar's old folder grouping of native SSH panes; an SSH tab now groups by host, and nothing else read it. - The file tree's context menu takes `cx` instead of `danger` and the new groups flag, back to the argument count it had on main. - A title test builds its workspace in the initializer. |
||
|
|
afcb8aa2dd |
feat(agents): quick launch for detected CLI agents (#961)
* feat(agents): quick launch for detected CLI agents (#955) Every agent whose launch program is on PATH becomes a palette command, "Agent: <name>", ordered by frecency and bindable as LaunchAgent:<slug>. "New Agent Tab" (Cmd+Shift+A on macOS; unbound elsewhere, where Ctrl+Shift+A is select-all) launches the most recently used one, and the New Tab menu gains a single "Launch Agent..." row that opens the palette pre-filtered to them. A launch always opens a new pane (a tab in the active tab's cwd, or a split when picked from the palette with Alt held) and types the command into that pane's shell once it exists - immediately for a local pane, on landing for a remote one via PendingSpawn::run_on_land - never into a pane that was already there. Detection, status and resume then work as for a hand-typed agent. The command is the agent's bare binary unless the new `agent_launch` config map overrides it. A wrapper named there is detected as its agent without an `agent_commands` entry: the daemon folds the programs `agent_launch` runs into its alias map (interpreters, shells and real agent names excepted), reloaded when config.json changes instead of once per process, and a mapped script run under its interpreter (`bash ~/bin/cc`, `node cc.js`) is now recognised too. A running agent's pane menu gets "Set Current Launch Args as Default", which writes its launch argv - minus session flags and positional prompts, joined with the settings' quoting - into `agent_launch`. Remote workspaces cannot be asked for their PATH through the Host trait, so they offer the agents previously seen running in that workspace (WindowView::seen_agents). * fix(agents): count only agents that start under a view, not ones reattached to A view rebuilt over a running pane - every agent tab after an app restart, or a workspace switched back to - saw its agent appear from nothing and reported it as detected, bumping that agent's frecency once per launch of the app. The terminal now remembers whether its link was an attach, and such a view only reports agents once its shell has been seen back at the prompt with no agent in front. Also pins down that the agents seen in a remote workspace, its quick launch list, persist in views.json with the rest of the workspace. * fix(daemon): probe the foreground as soon as a new program takes it The foreground probes ran on output only, at most once per 500ms interval. A quick launch types the agent's command the moment the shell is up, so the agent drew its whole first screen inside the interval of the prompt it was typed at and then waited for a key: the pane never learned it was running an agent until something else printed. Seen in a dev instance, where a launched Claude Code stayed undetected at its trust prompt. The reader now asks the pty for its foreground process group on every read (one ioctl) and probes immediately when it changes. |
||
|
|
27483e893d |
feat(ui): collapse the New Tab menu's shell list to three by frecency (#960)
The + menu listed every shell the machine reports (nine on a stock macOS box)
above the SSH hosts. The Local section now names the default shell, always
first, then only shells that have actually been opened, by frecency, three rows
at most, the same way the SSH section caps its hosts.
Shell usage is recorded in a new `shell_frecency` config map, keyed by the
inventory label, bumped from both the menu row and the palette. Every shell is
now a palette command titled "Shell: {label}" (same word in every locale), and
an "Other Shells…" row opens the palette pre-filtered to them. That row is
hidden when the menu already names the whole inventory. Running a shell command
from the palette respects the ⌥/Alt split modifier like the menu row.
|
||
|
|
bd0dd22bfa |
feat(tabs): hibernate a tab to free its memory and wake it later (#954)
A tab can be put to sleep from its context menu or the command palette: its panes are stopped (screens kept on disk), the tab keeps its place in the sidebar, and selecting it wakes it through the same restore a reboot runs, resuming a supported agent's session. The sleep mark lives on the machine tree, so it survives app and daemon restarts. Closes #762 |
||
|
|
de15f9b0ab |
feat(ssh): keep saved hosts in servers.json and add an SSH tab title setting (#952)
Saved SSH hosts and their usage counts move out of config.json into servers.json beside it, so config.json can be synced between machines without carrying a server list (#911). An older config.json is split on first load: servers.json is written first (0600), then only the two keys are removed from config.json, leaving every other key as it was. When both files hold hosts, servers.json wins and the stale copy falls out of config.json at its next save. A servers.json that does not parse is kept aside and blocks saves, as config.json does; hand edits hot-reload. Settings -> Window & Tabs -> SSH tab title (`ssh_tab_title`) pins an SSH tab to the profile name (saved host name, ~/.ssh/config alias, or the address typed for a quick connect) or the hostname, on the OSC title's rung of the existing label ladder: a renamed tab still wins, OSC titles are still tracked, local panes are untouched (#726). |
||
|
|
7666e2fd83 |
Merge pull request #941 from l0ng-ai/feat/852-portable-mode
feat(windows): portable mode keeps data beside the executable |
||
|
|
386ccc3d81 |
Merge pull request #939 from l0ng-ai/feat/720-font-smoothing
feat(fonts): make macOS stroke thickening configurable |
||
|
|
2eb1a79d76 |
Merge pull request #935 from l0ng-ai/feat/754-dock-preview-wrap-actions
feat(editor): add ToggleDocumentPreview / ToggleDocumentWrap actions |
||
|
|
f5a97ed68c |
feat(windows): portable mode keeps data beside the executable (#852)
A portable ZIP install whose folder has both the .tty7-portable marker and a data\ folder next to tty7-app.exe now uses <exe dir>\data as its config directory, so settings, sessions, scrollback and the daemon's socket travel with the program. --config-dir and TTY7_CONFIG_DIR still win. The folder is an explicit opt-in because the marker has shipped in every portable ZIP since the in-app updater arrived: keying on it alone would move existing users onto an empty directory at their next update. The portable directory is not the machine's config dir, so a portable copy never adopts the installed tty7's legacy tree. An updater test pins that data\ is not a managed root and survives an in-place update. |
||
|
|
592058acc7 |
feat(fonts): make macOS stroke thickening configurable (#720)
Add a macOS-only `font_thicken` key (default true) and a Settings row under Appearance > Terminal text. When off, AppleFontSmoothing is pinned to 0 in this process's NSArgumentDomain before gpui's text system first reads it, so glyphs render at the face's own weight. The volatile domain is in-memory only: nothing is persisted and no other app is affected. gpui caches the preference in a OnceLock, so a change applies after a restart; no gpui fork change is needed. |
||
|
|
962fd87ea5 |
feat(tabs): step to the next/previous tab without the switcher (#867)
Add SelectNextTab / SelectPrevTab, which move to the neighbouring tab in the order the strip or sidebar shows them, wrapping, with no popup. Defaults: Cmd+Shift+] / Cmd+Shift+[ on macOS, Ctrl+PgDn / Ctrl+PgUp elsewhere. The tmux preset's prefix n / p now bind these (tmux next-window semantics) instead of the MRU switcher, which never auto-committed there. NextTab / PrevTab keep their config names and Ctrl+Tab, but are labelled Recent Tab Switcher on the Keybindings page; the palette's Next/Previous Tab entries now show the SelectNextTab chord they actually run. |
||
|
|
9f842975ce |
feat(editor): add ToggleDocumentPreview / ToggleDocumentWrap actions (#754)
The code panel's Preview/Edit and Wrap buttons only toggled state in their click handlers, so there was no way to reach them from the keyboard. Register both as actions, unbound by default like ToggleDocumentFill and the DocumentWidth* actions, list them in the command palette and the Keybindings page, and route the buttons through the same methods. Instead of config keys for the initial state, the last-used state is remembered (editor_soft_wrap / editor_markdown_preview), the way diff_view and scm_graph_expanded already are. A file opened at a line target always opens as source so the cursor is visible. |
||
|
|
a26dab532a | Align settings terminology and documentation navigation | ||
|
|
ec08949625 | Keep About focused on app information and update status | ||
|
|
a4dbdc7bf1 | Redesign settings navigation, search, and editing workflows | ||
|
|
ecd5c31d39 |
docs: name the settings field that picks a local pane's shell
The local remedy sent the reader to `config.json` for something the app has a field for — **Settings → Terminal → Shell → Program**, with the **Arguments** box beside it that turns the injection off when filled. Claude-Session: https://claude.ai/code/session_01JRqYZ9E153WpSHGS2AW3BM |
||
|
|
bc5e6b6117 |
docs: correct the nested-shell cwd notes against the code
The oh-my-zsh guard is not at the top of `lib/termsupport.zsh` — it fences off the cwd reporter at the end of the file, and the title hooks above it keep running over SSH. Say that instead. The list under "the shell is one tty7 does not integrate" is the set it *does* integrate, which read as its own opposite; turn the sentence around. Nushell has had the integration since #637 (`ShellKind::Nushell`, a `nu --config` wrapper), so give it a row in the shells table and stop listing it among the shells that have none. The per-profile toggle lives behind **Advanced**, as `docs/remote/ssh.mdx` and the section below already say, and the panel is the **Files** panel everywhere else in these docs. Claude-Session: https://claude.ai/code/session_01JRqYZ9E153WpSHGS2AW3BM |
||
|
|
4af4452900 |
docs(shell-integration): explain why a nested shell stops reporting its cwd (#698)
tty7 injects shell integration into the shell it launches for a pane, and only that one. A shell the user starts by hand afterwards -- `zsh` typed at a bash prompt -- is a new process nobody injected into, so it emits no OSC 7 and no OSC 133, and the pane keeps showing whatever directory the outer shell reported last. On a local machine that is invisible, because oh-my-zsh reports OSC 7 itself from lib/termsupport.zsh. Over SSH it is not: that file returns early when SSH_CLIENT or SSH_TTY is set, which sshd always sets, so on a remote host neither oh-my-zsh nor tty7 reports the directory of a hand-started zsh. That combination is the whole of #698, and it is why the reporter sees it work over SSH under bash and stop under zsh. Documented in two places: a "Shells you start yourself" section in the shell integration reference, and a troubleshooting entry that names the symptom the way a user would ("the directory stopped following my shell"). Both give the two remedies -- `chsh -s /bin/zsh` on the remote host, which is what moves the integration into the shell tty7 bootstraps, and, when the login shell cannot be changed, a four-line precmd hook that reports OSC 7 by hand. The entry also says why a login script or `exec zsh` from .bashrc is not a fix: both run after (or instead of) the shell tty7 set up. The same pages now describe the process-inspection fallback honestly. It was one parenthetical -- "tty7 falls back to inspecting the process" -- which overpromises: it exists only where the daemon can see the pane's processes, so a local macOS/Linux pane or a remote-workspace pane gets it and an SSH pane or a Windows pane does not, and it is a poll driven by pane output at most twice a second, not a report, so it trails a `cd`. That poll is the mechanism behind the issue's second, unconfirmed half: with tty7-server on the remote host the daemon is on that host, so a nested zsh's cwd does get picked up out of /proc -- late, and only when the pane writes something. Deliberately no code. Propagating the integration into a shell the user starts by hand would mean exporting ZDOTDIR (or rewriting the user's startup files) from every pane, which leaks into every zsh in the session including scripts, and the remote bootstrap deletes its throwaway ZDOTDIR at the first prompt precisely because an SSH session has no reliable exit hook -- a directory that outlives its deletion is exactly what would break a nested shell rather than help it. Tightening the cwd poll for uninstrumented panes is a real possibility, but it is a Linux-only daemon path that cannot be exercised from here, for a symptom nobody has yet reproduced; the mechanism is written down instead. Claude-Session: https://claude.ai/code/session_01UUyWQXzcBAoBzaSX8pc7nU |
||
|
|
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 |
||
|
|
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. |
||
|
|
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 |
||
|
|
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> |
||
|
|
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> |
||
|
|
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. |
||
|
|
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. |
||
|
|
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. |
||
|
|
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. |
||
|
|
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. |
||
|
|
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.
|
||
|
|
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> |
||
|
|
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> |
||
|
|
4da3868797 |
feat(shells): let the new-tab menu carry entries the user wrote (#534)
Closes #443 |
||
|
|
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 |
||
|
|
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> |