Files
tty7/docs/window/side-panel.mdx
T
l0ng-ai 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 `#`, `\`, `&#47;`)
  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&#58;///...` 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.
2026-09-27 19:04:42 +08:00

183 lines
8.7 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
title: "The side panel"
description: "Info, Files, Search, Source Control and GitHub — plus the built-in editor."
---
<kbd>⌘ J</kbd> opens a panel on the right of the window. It is hidden by
default; whichever tab you leave it on is where it opens next time.
The icons at the top switch tabs — hover one for its name — and clicking the
lit one puts the panel away again.
<Frame caption="The Info tab beside a terminal: session, processes, listening ports">
<img src="/images/side-panel.webp" alt="The tty7 side panel" />
</Frame>
## Info
Everything tty7 knows about the focused pane, in one column:
| Section | What it shows |
|---|---|
| **Session** | working directory, shell, SSH connection, git branch, `+N −M` changes, and the coding agent with its status (idle / working / waiting / done) |
| **Processes** | the process tree inside the pane, with the foreground process marked |
| **Ports** | every port those processes are listening on |
Hovering a row shows what it can do at the right-hand end of it: **Copy** on the
working directory, the SSH host and the branch, and **Reveal in Finder** / **Open
Folder** alongside it on a working directory this machine can see. The `+N −M`
counts open the [diff overlay](/git/diffs), the same click the sidebar's counts
answer to — and the same **Settings → Window & Tabs → Open diff preview from
sidebar counts** turns both of them back into plain text.
The ports section is the quickest answer to "what is this pane serving, and
where" — the same data `tty7 procs` prints. A port row copies `localhost:PORT`,
and on a local pane opens it in your browser.
## Files
A file tree rooted at the pane's working directory, with git status decorations
on every row and a search box at the top. The search box carries a clear button
while there is something in it.
- **Click a file** to open it in the built-in editor.
- **Drag a file out** to Finder or Explorer to copy it there.
- **Drag files in** from the desktop to copy them into the folder under the
cursor. A folder row takes them itself, a file row stands in for the folder
holding it, and the empty space below the tree means the top of it. Folders
come in whole, the executable bit survives, and a name that is already taken
is asked about rather than replaced.
Both directions work over a [remote workspace](/remote/workspaces) too, reading
on one machine and writing on the other, up to the size one control frame can
carry — past that the panel tells you to use [SFTP](/remote/sftp).
## Search
Find in files across the project the Files tab shows: the repository root of
each pane in the tab, or its working directory outside a repository. Opening the
tab — from its icon, **Right Panel: Search** in Search Everywhere, or the
`ShowRightPanelSearch` action — puts the cursor in the search field.
Results arrive as you type, grouped by file with a count per file; click a
file's row to fold it away. Each hit is its line with the matches highlighted,
and **clicking one opens the file in the built-in editor with the cursor on that
line and column**. <kbd>Enter</kbd> in the field searches again, for when the
files changed underneath you.
The three toggles at the end of the field:
| | |
|---|---|
| **Aa** | Match case — off by default, so `needle` finds `Needle` |
| **ab** | Whole word — `foo` no longer finds `foobar` or `foo_bar` |
| **.\*** | Regular expression, in Rust [`regex`](https://docs.rs/regex) syntax. A pattern that does not parse says why in red instead of searching |
What is searched is what the tree shows: a folder's `.gitignore` applies whether
or not it is in a repository, and dot-files and dot-directories are skipped.
Binary files and files over 1 MB are stepped past. A search stops at 2,000
matching lines, 100 per file, 20,000 files or ten seconds, whichever comes
first, and says so under the results — narrow the query to see the rest.
In a [remote workspace](/remote/workspaces) the search runs on the remote
machine and only the hits cross the network. A remote `tty7-server` older than
this feature cannot search; the tab says so and asks you to update the server
rather than showing an empty result. An SSH pane's files are browsed over
[SFTP](/remote/sftp), which cannot search them — open the host as a remote
workspace for that.
## Source Control
The git panel for the focused pane's repository, in four groups — **Merge
Changes**, **Staged Changes**, **Changes**, **Untracked**. Write a message,
commit, and push without leaving the window.
[Source control →](/git/source-control)
## GitHub
Issues and pull requests for the repository the focused pane is in, read-only.
The tab reads the repository's remotes and binds to the one on `github.com`; in a
fork, where `origin` is yours and `upstream` is the project, it picks
`upstream`, and the name at the top is a menu of every GitHub remote when there
is more than one. GitHub Enterprise hosts are not supported.
- **Issues | Pull Requests** and **Open | Closed** switch the list, newest
activity first, 50 at a time with **Load more** underneath.
- Each row carries a state glyph whose *shape* says open, closed, not planned,
merged or draft; the number, author and last update; and the labels.
**Click a label** to show only that label, and the × beside it to clear it.
- **Click a row** for the detail: title, state, author, labels, the description
and the conversation as Markdown. A pull request adds its branches, size and
changed files — click a file to open its patch in the
[diff overlay](/git/diffs).
- The ↗ button opens the repository, or the issue, on github.com; ↻ refreshes.
Lists are kept per repository, so switching tabs or panes does not fetch them
again; anything older than two minutes is refreshed in the background while
it stays on screen.
### Signing in
There is nothing to sign in to in tty7. It reuses the GitHub CLI's login: the
token comes from `GH_TOKEN`, then `GITHUB_TOKEN`, then `gh auth token`. `gh` is
looked for on your `PATH` and in the usual Homebrew and `/usr/local/bin`
locations, so it is found even when tty7 was opened from Finder.
Without a token, public repositories still work, at GitHub's unauthenticated
limit of 60 requests an hour. A private repository, or a spent limit, says so
in the panel and suggests running `gh auth login`; press ↻ afterwards and the
new login is picked up without a restart.
The Info tab gains a **GitHub** row for a repository with a GitHub remote. It
opens the branch you are on, on the remote it tracks — or the repository's page
for a branch you have not pushed.
Markdown from GitHub is written by strangers, so it is rendered with two
changes: only images GitHub hosts itself — the screenshots pasted into an issue
— are loaded, and any other image becomes a link you can open in the browser;
and links that would open anything other than a web page or an e-mail are
disabled. See [what leaves your machine](/reference/privacy#what-leaves-your-machine).
## The editor
<kbd>⌘ ⇧ E</kbd> toggles the code panel; clicking a file in the Files tab opens
it there. It is a real editor — syntax highlighting, line and column readout,
wrap toggle, and a Markdown preview — meant for the edit you would otherwise
have opened `vim` for.
| | |
|---|---|
| <kbd>⌘ S</kbd> | Save |
| <kbd>Esc</kbd> | Back to the terminal |
Files are watched on disk: a change underneath you is picked up, and closing
with unsaved edits asks before discarding them. Files over 4 MB and anything
that looks binary are refused with a note rather than opened badly.
### Where it opens
The editor docks beside the terminal, taking half the space between the sidebar
and the right panel. The terminal keeps running, stays visible, and stays
typeable — click it, read what your agent said, click back. Diffs open in the
same column.
Drag the divider for any width between a fifth and four fifths of that space —
the range `document_ratio` keeps — or double-click it to cycle a third, a half
and two thirds. **Document: Third / Half / Two-Thirds Width** in Search
Everywhere do the same. On a narrow window the terminal's own floor stops the
divider sooner.
Right-click the document's header for **Fill window**, which is the old
full-workspace overlay, unchanged. **Document: Fill Window** and **Document:
Dock Beside Terminal** in Search Everywhere are the same switch.
Fill or dock is **per tab**: read a long file over the whole window in one tab
while an agent keeps half of another, and neither moves the other. A fresh tab
starts from `document_layout` in `config.json`. The width is shared, and
persists as `document_ratio`.
A window too narrow to give both the terminal and the document a readable width
fills for that file only — widen it and the column comes back, without your
setting having changed.