chore(release): v26.8.3

This commit is contained in:
l0ng-ai
2026-08-12 18:41:37 +08:00
parent 473c94ecba
commit 71c6783fb4
3 changed files with 542 additions and 7 deletions
+537 -2
View File
@@ -5,10 +5,139 @@ All notable changes to tty7 are documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [Unreleased]
## [26.8.3] - 2026-08-12
### Added
- **A full Source Control panel** — the half-finished Changes tab is now a
working-tree panel with a real index. Files are grouped into merge, staged,
changes and untracked, with a file appearing in two groups when its status
says so; the rows stage, unstage, discard and open a diff, and a commit can
be written, amended, pushed, pulled or synced from the panel itself. The file
tree carries git decorations beside every entry, folded once per refresh
rather than once per frame. A diff opens from three sources — working tree,
staged, or a commit — behind a unified/side-by-side toggle that names which
one it is showing. Under the file list sits a commit history drawn with
lanes, paged as you scroll, with lane colours derived from the theme's own
`ansi16` and walked to a contrast floor so they stay apart on every builtin.
Anything that can lose work asks first, and the data layer is the single
place that decides what counts as destructive. **The control protocol does
not change** — an unmodified remote server serves all of it, push, pull and
fetch included, because the deadline that would have cut a long network
operation short was only ever enforced client-side. Non-UTF-8 paths are a
documented read-only limitation: those rows carry a tooltip rather than
sending git a pathspec nobody can spell. (#424)
- **The interface has a font size of its own** — `ui_font_size` in
`config.json` scales the whole chrome, and every window's root sets it, so
sidebar, panels, menus and dialogs move together. It defaults to gpui's own
16, so an existing config renders exactly as before. The terminal grid is
unaffected: it stays absolute pixels off `font_size`, which means a display
that is not Retina can finally have readable chrome without touching the text
in the panes. The detail panel, which had carried a private run of pixel
sizes that put its primary text at the size everything else uses for
secondary text, is back on the same ladder as its neighbours, with mono a
notch under the sans it pairs with.
- **A scrollback scrollbar down the right edge of a pane** — a pane's scroll
position lives in rows of scrollback rather than pixels of laid-out content,
so it had no handle to give a scrollbar. It now implements one over the grid
and draws the same bar the sidebar and every list already use — same theme,
same fade-out. The bar never drives the terminal: it records the row it
wants and the next render applies it, clearing the sub-line remainder and
cancelling an in-flight smooth scroll on the way. Scrollback piling up at the
live edge is deliberately not reported, so a pane printing a build log does
not hold a thumb on screen for as long as the output runs; every other
change passes through, including the history shrinking. (#480, #432)
- **Zoom a pane's font with the platform modifier and the wheel** — holding
Cmd (Ctrl off macOS) and scrolling over a terminal resizes the font instead
of the scrollback, which is what you reach for when showing a pane to someone
else. A wheel detent is one step whatever the platform bills it as, and a
trackpad accumulates until the fingers have travelled three lines, so a flick
does not run the font end to end. Steps go out as the existing
increase/decrease actions, so the clamp and the saved setting stay in one
place.
- **The command palette previews a theme while you arrow through the list** —
picking a theme used to close the palette, so seeing what one looked like
meant reopening it and retyping the search for every candidate. The
highlighted row is now applied to the running window and the picker stays
open: Return keeps it, Escape or any other way of closing puts the previous
theme back. A preview only touches the in-memory config, so arrowing through
the list never writes `config.json`, and the picker opens on the theme
already in use so opening it changes nothing by itself.
- **The palette matches commands in every locale, not just the one on screen**
— the filter only ever saw the label it was rendering, so a window running in
Chinese answered "no matching commands" to `theme`. Each entry now carries
every locale's wording of its key, plus the stable command id, as hidden
search aliases: built once with the entry, never rendered, and scored just
below a hit on the visible label so what you can actually read still comes
first.
- **fish history joins the Ctrl+R menu and inline completion**, locally and on
remote hosts. `fish_history` looks like YAML and is not — fish escapes only
`\` and newline and quotes nothing, so a YAML reader silently drops every
record holding a `: ` or a leading `[` and truncates anything with a ` #`,
which is most conventional-commit messages. It is read with a line scanner
shaped like fish's own reader instead. Each history file fetched from a
remote host now carries the name it came from, so the far end's fish records
reach the fish reader rather than arriving as literal `- cmd:` rows.
Multiline entries are skipped rather than half-recalled. (#421)
- **The daemon upgrades in place instead of killing every shell** — picking up
a new build used to mean stopping the daemon, and stopping the daemon takes
every pane with it: the pty master is a descriptor that process holds, so its
death raises SIGHUP on the shell, the agent and the half-finished command.
The daemon now rewrites itself with `execve` on handoff — same pid, same
children, same descriptors, same locks — writing what it knows about each
pane into a blob and clearing `FD_CLOEXEC` on the ptys first. The restart
Settings offers for a quiet moment is still there for the cases handoff
cannot cover. (#449)
- **The tab sidebar elides labels tail-first and explains itself on hover** —
a path keeps its leaf rather than its root, anything that is not a path keeps
both edges, and a hover card spells out in full whatever the row had to hide:
a renamed tab's real name, the terminal title behind a `Shell 3` placeholder,
the remote host. The card is decided by comparing what the row rendered
against what it rendered it from, so it never opens on a row that hid
nothing. (#446)
- **The Info tab's rows do what they show** — the Session table used to render
every fact the same inert way, with two unlabelled actions in a strip four
rows below the path they acted on. A `changes` row is now the sidebar's green
and red `+N M` and opens the same diff overlay; an agent row wears the
status dot its tab does; a port row hands over its address instead of leaving
it to be retyped; and what a row can do appears at the end of it on hover, in
the strip the Source Control rows already use. A port is only offered as
`localhost` when localhost actually reaches it — a server bound to
`172.17.0.1` is named by the address it really holds. (#531)
- **Closing a tab asks before it ends work that is still running** — ⌘W was the
one action that permanently ended a shell in an app whose headline claim is
that shells outlive it, and a mid-turn coding agent, a forty-minute build and
an idle prompt all died identically and silently. Reopen Closed Tab was never
an undo: it restores the layout with a *fresh* shell. Closing now asks once,
naming what it would end, with Keep as the default button; an idle prompt
still closes without a word. A pane counts as busy only when the shell is
actually reporting, so a terminal without shell integration is not held
hostage by a question it cannot answer. Close Other Tabs and Tabs to the
Right skip anything that would raise a question rather than stacking one
dialog per tab.
- **The opencode plugin reports its session id**, so a restarted pane can
resume the same session with `opencode --session <id>`. It also maps
`session.status` busy/idle for versions that no longer emit `session.idle`. A
task-tool subagent runs in a child session whose events are structurally
identical to the pane's own, so the bridge remembers child ids and lets their
events pass without touching the pane's session — otherwise the pane would
report and resume the subagent's session, and a subagent going idle would
call the pane done. (#481)
- **A documentation site** — the docs are now a Mintlify site rather than a
handful of Markdown files in the repository. (#478)
- **Drop files into the Files panel to copy them in** — the panel has always
been a drag *source*; it now takes a drop as well. Files dragged in from the
desktop land in the folder under the cursor: a folder row takes them itself,
@@ -144,6 +273,66 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Changed
- **Every confirmation answers the way the platform taught you it would** — the
action button is on the right and Cancel on the left, through one shared
helper. Answer 0 is drawn rightmost and takes Return, so the old
`&[Cancel, Delete]` put Delete exactly where a decade of macOS trains people
to click Cancel. Escape also cancels now, in all nineteen of them: gpui only
binds it on a button declared as the cancel, and every call site had been
passing plain strings.
- **A search match is washed in the theme's accent, at a strength the theme can
afford** — the wash was drawn from the terminal palette's selection colour at
a fixed 1.45:1 against the background, which is less than a hairline is worth
spread over a whole cell, on a grid that is already grey on grey. The tint is
now the accent, the one colour the terminal surface has nothing else in, and
its strength is derived per theme from that palette's own text-contrast
budget rather than from a constant that has to be safe for the worst
palette. The current match drops its caret-coloured outline, which existed
only because the fill could not say "this one" on its own. (#470)
- **The block cursor is drawn as reverse video** rather than as a 55% tint that
halved whatever contrast the caret had. On a wide character it inverts the
half holding the glyph.
- **An inactive pane dims by blending its terminal colours toward the window
background** instead of being faded as a whole, and the fade is now worn by
the terminal rather than by the pane — which is what keeps the grab grip
legible on exactly the panes `dim_inactive_panes` fades, the ones being
reached for. (#464)
- **The pane grip is three dots, the way Ghostty draws its own** — the bar a
pane was picked up by grew and recoloured under the pointer and appeared
whenever the pointer was anywhere in the pane at all: a mark on top of the
terminal wherever the mouse rested, and a target that moved while being
reached for. It is now three dots with 80×12 of reach around them and nothing
between the two states but ink, asked for by the pane's top fifth rather than
by the whole pane. The target is there for as long as the pane can be moved
and only the dots come and go, so a pointer going straight for the top of a
pane can press the grip on the frame it arrives; a 150 ms fade makes them
read as arriving rather than blinking. (#463)
- **A WSL distro list comes from the registry rather than the WSL service** —
`wsl -l -q` has to reach the service, and reaching the service is the part
that can be slow, behind a hardcoded 3 s timeout that made the listing
all-or-nothing. On a machine where one round trip takes 3.3 s the call timed
out every time and no distro was ever offered in the shell menu: not slow,
absent. `Lxss` is where `wsl.exe` registers them, nothing is listening on it,
and it cannot hang; `wsl -l -q` stays as the fallback for when the key will
not open at all. Distros are filtered by `State`, so a cancelled install or a
failed `--import` no longer arrives in the menu and opens a pane that dies of
a registration error.
- **Opening a WSL tab costs one probe instead of two** — every pane ran the
readiness probe from the client and then the daemon ran the identical probe
before opening the link: two rounds of five serial `wsl.exe` calls to learn
one fact, and the client's copy threw its answer away and kept only the
error. A distro also says once where its server is and is not asked again,
rather than re-proving `uname`, `$HOME`, a stat and a liveness check on every
pane. Measured on a distro already running and connected: 800 ms to open a
tab, down to 440 ms — and on the machine in #454, where a round trip takes
3.3 s, the duplicate alone was costing 15 s a tab.
- **`tty7 pane close --json` now reports `{"closed": [ids]}`** rather than a
single `{"closed": id}`, because the verb takes more than one pane. A batch
that could not close everything exits 1 with `{"closed": […], "failed": […]}`
@@ -151,6 +340,342 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Fixed
- **Ctrl+C works in a pane on Windows again** — the daemon was created with
`CREATE_NEW_PROCESS_GROUP`, which disables Ctrl+C for the whole new group,
and Windows hands that state down to every descendant. Every ConPTY shell a
pane spawned inherited it and so did everything those shells ran: the pane
wrote 0x03 and conhost turned it into a keypress, but the `CTRL_C_EVENT`
never came, so `go run` and `npm install` carried on regardless. Git Bash
looked fine only because MSYS synthesises SIGINT from the byte itself.
`DETACHED_PROCESS` already left the daemon without a console for a control
event to arrive on, so the group flag bought nothing to begin with; both
daemon spawn paths and the headless CLI server now share one constant
without it, and a pane clears any inherited ignore before it opens the pty,
so a tty7 launched from a shell that already had the bit set is covered too.
(#459, #451, #314)
- **Two tty7s on different config directories no longer erase each other's
workspaces** — the machine tree resolved from `$HOME` while everything else
an instance owns resolved from the config directory, so `--config-dir` moved
every part of an instance except the one that says which workspaces exist.
Two daemons, each holding its own lock and each certain it was the only
server on the machine, co-owned one `machine.json`; the tree is written
whole, so the second to flush replaced the first's workspaces with its own,
and the next daemon to start read the survivor's tree as the machine's. An
empty tree is indistinguishable from a machine that really has nothing on
it, so the GUI did what an empty tree means and forgot those workspaces for
good. A lock and the thing it protects have to be keyed alike. The daemon
adopts a legacy file on startup so the move itself loses nothing, and only
the instance running out of the config directory the machine resolves to on
its own is entitled to that adoption — otherwise a default install beside a
`--config-dir` one would rename the machine's tree into the wrong place.
(#462)
- **A remote window that lost its first tree pull no longer sits empty until
you restart** — a window opening onto a remote workspace is blank until the
machine's tree is pulled and its tabs rebuilt, and a failed pull recorded a
debt on the promise that the next sync would settle it. Nothing settled it:
the hook that repays the debt ran for the local host and nowhere else, so on
a remote machine only a full reconnect, an edit in the window, or restarting
the app ever cleared it — and an empty window has nothing in it to edit. The
window sat on the home page with every tab and every shell still alive on the
machine. Both routine ways to fail a pull with a healthy link are covered
now: a tree read that overruns its ten seconds, and a workspace create that
loses a race with the priming pass running the same create from the other
side of the same window opening — that one is no longer treated as a failure
at all, the tree is simply read again. The retry is backed off, the backoff
ends with the run of failures rather than with the next success, and a window
left open on a machine that is really gone stops logging at warn once the
backoff settles at its cap. (#472)
- **The macOS DMG builds again** — every release and nightly macOS job had been
dying in packaging with `hdiutil: create failed - No space left on device`,
after the build, the signing and the notarization had all succeeded. The
runner's disk was never full: the path in the error is the image being
created, so it was the volume that ran out. `hdiutil create -srcfolder` sizes
the image from the bytes it is about to copy and does not cover what the
filesystem spends carrying them, so a bundle that fits by measurement still
runs the volume dry partway through. The room is asked for explicitly now —
twice the content plus 64 MiB — and since the image is compressed on the way
out the slack is nearly free: 127 MiB of empty volume costs about 672 KiB in
the published DMG. This was a threshold rather than a cliff, which is why it
began without anyone touching packaging. (#477, #476)
- **The CLI and the GUI agree on what exists** — five places where a workspace,
a tab or an attachment was real on one side of the socket and invisible on
the other, all rooted in the GUI keeping its own list of which workspaces
exist and consulting the machine tree only for the ones already in it, so
anything another client created was unreachable by construction. The switcher
now lists workspaces the machine holds that this client has never opened, and
opening one keeps its id instead of claiming a fresh one; opening a workspace
hydrates from the machine's tabs rather than saving an empty session over
them; a workspace removed with `ws rm` stays removed instead of being written
back under the same id and haunting the switcher until a restart; `tty7 ls`
can name the host holding a workspace; and `tab ls` / `ws tree` fall back
through name, agent, cwd leaf and process name rather than showing a blank.
(#423)
- **A pane the CLI created can be attached to** — a pane's owner names the
workspace allowed to attach to it, and the CLI wrote a literal `tty7-cli`
there for every pane it made. A window opening on a CLI-built workspace found
none of them attachable: it spawned a fresh shell per tab, orphaned the live
ones, and — because the tree still carried each pane's agent session — greeted
the user with a failing `claude --resume <id>` in every one of them. Both
spawn paths pass the workspace id now, and restore treats an unparseable
owner as no claim at all, so panes stamped by an older CLI attach instead of
stranding. `pane ls --all`'s OWNER column shows a dash when it agrees with
WS, so what is left in it is exactly what is worth reading. (#425)
- **A WSL pane starts when the distro's default shell is fish** — `wsl.exe --`
hands the command line to that default shell, which parsed the POSIX
bootstrap before `sh` could receive it, so the pane never started at all.
Every invocation carrying an argv goes out under `--exec` now. Those panes
then came up with no shell integration, because the bootstrap only had a bash
arm: no OSC 133 and no OSC 7, so no prompt marks, no exit status, no cwd, and
`tty7 wait` and busy/idle status dead in the pane. There is a fish arm now,
POSIX-quoted since `sh` parses the script rather than the user's own shell.
(#422)
- **A remembered WSL server path repairs itself** — the note saying where a
distro's server lives had no working way to be wrong: its only repair fired
when spawning `wsl.exe` failed, and `wsl.exe` starts perfectly happily with a
server path that no longer exists inside the distro. The exec failure arrives
later as an EOF on the bridge, so a distro that was reinstalled or had its
bin directory cleaned out failed every WSL tab from then on with no way out
but restarting tty7. A bridge that closed without ever sending a byte never
ran, and after one of those the next pane proves the distro again. The note
is also read under the install lock now, so a window restoring panes can no
longer spawn the binary that a server update is in the middle of replacing.
- **A half-read registry is no longer passed off as the distro list** — the
walk ended on any non-zero return and reported what it had, but only one of
those returns means "that was all of them"; the rest mean it stopped early,
and a failure at the very first index came back as an authoritative "there
are no distros". The sweep behind the shell menu keeps its last good list
only when the probe declines to answer, so that empty answer erased the
user's distros for the length of the TTL, with no error anywhere. The walk
says nothing now unless it reached the end.
- **The Source Control panel is on the same font scale as the rest of the
window** — the panel was cut from a branch that copied the right panel's size
ramp hours before the interface font scale landed on main and moved that ramp
onto rems. The two sides touched different files, so the merge had nothing to
conflict over and the panel shipped a step small and deaf to `ui_font_size`
entirely — its primary text at the size its neighbours use for secondary
text, and a file row mixing a rem-sized status letter with a pixel-sized
path. Every size a reader reads is a rem now; pixels stay on what type sits
inside, and the row pitches and section heights grow with the type they hold.
The commit body's fold also folds: `line_clamp` limits the wrapped lines
within one logical line, and a commit message is the one string this panel
draws that is always hard-wrapped, so a clamp of four over a thirty-line body
laid out all thirty and Show more was a label with nothing behind it. It
clips a height now. (#557)
- **A search highlight stays on its text while the pane is printing** — a match
point is a line of the grid it was scanned from, so every line that scrolled
off the top left each highlight washing the row its text had just left. The
stored points now slide with the text on every wakeup. The rescan behind them
was purely debounced, so a pane that never pauses — an agent streaming a
reply — never got one at all: the count froze and the highlights sat on text
that had since been rewritten in place. Output can now only push the deadline
out so far. (#559)
- **A machine's name gets its own line in the switcher** — a group header drew
the machine name and its `user@host:port` on one row, and the endpoint was a
fixed-width item that claimed its space first, so a machine whose name ran
long read as a stub: `y...`, `GAEM...`. Both the header and the "other
machines" rows use the two-line shape the workspace rows under them already
use, with the name backed by the full row width and the endpoint and link
status underneath. Rows with neither stay single-line. (#560)
- **The stray guide rail under a remote machine is gone** — expanding one in
the switcher drew a 1px vertical line left of the workspace avatars that
local groups never drew, so it read as a stray mark rather than an indent
guide, and it stopped short of the last row's midpoint leaving a dangling
stub. Remote rows indent exactly like the SSH host rows below them now.
(#536)
- **The workspace discs in the switcher are legible** — the monogram disc was
drawn at 0.55 opacity on every row except the current workspace, and the
initial inside it is already the foreground at 0.65, so the letter landed at
about 0.36 and went illegible on exactly the rows the panel exists to let you
choose between. The liveness dot is painted on the wrapper rather than inside
the disc, so it never dimmed with it — a washed-out grey blob with a
full-strength green dot stuck to its corner, which is what made the column
look dirty rather than quiet. The dimming is gone; the current workspace was
already saying so three louder ways. (#483)
- **Overlay scrollbars fade out again** — macOS reports that scrollbars should
not auto-hide for anyone with a mouse plugged in, and that was turned into
"always show" for every list in the app. The preference is about legacy
scrollbars, which take a gutter out of the layout; tty7's are overlay bars
painted on top of the content, so "always" parked an opaque bar over the
switcher's tab column for as long as the panel stayed open, with nothing to
fade it. (#471)
- **A tab label is cut on grapheme clusters, not on chars** — the sidebar's
elision measures against real glyph widths but sliced by `char`, so it could
satisfy every width check and still hand back a torn cluster: a
zero-width joiner with nothing in front of it, a variation selector landing
on the ellipsis, half a flag rendering as a bare letter. Scanning budgets
from 30px to 200px over emoji fixtures, 48 widths produced output no font can
render as intended. A tab title carrying an emoji is not exotic — plenty of
TUIs and coding agents put one there. Widths are unchanged, since clusters
are measured the same way chars were. (#450)
- **A multiline history entry draws on one menu row** — a command composed in
the inline editor keeps its newlines when it goes into the in-session
history, and gpui breaks text on `\n` whatever the white-space setting says,
so one such entry painted a dozen lines inside a one-line row and covered the
whole reverse-search menu. Line breaks are folded to a visible `↵` when
drawing — one char for one char, so the fuzzy matcher's highlight positions
still line up — and the stored entry is untouched, so recalling and running
it are unchanged. (#436)
- **One Restart server button on the Settings page** — the in-place-update
notice carried its own while the Server section right below it carried an
identical one. The notice moved into that section, so the single button that
ends every running pane is the only one on the page. (#452)
- **Several machines with outdated servers ask one question at a time** —
reconnecting to them raised one native modal per machine in a single pass,
stacked on each other, each about a machine the one in front of it did not
name. The queue is walked one question at a time now, and if the window goes
away with a question open the rest go back on the queue instead of being
swallowed.
- **Gestures that did nothing at all** — a pass over the whole app, most of it
found by driving the UI rather than by reading the code:
- **Escape in a confirmation dialog** cancels, in all nineteen of them.
- **⌘P then Return** now runs the first row. The list started with nothing
selected and only picked a row when a *query* changed, so nothing showed
what Return was aimed at.
- **The palette's `→ edit` badge** is `⌘E`. Neither advertised gesture could
fire — `→` is eaten by the query field and `⌘↵` by Enter Full Screen — so a
saved SSH profile could not be opened for editing from the palette at all.
- **⌘G past the last match** wrapped to match 1 and left it behind the
floating search bar, reading "1/3" with nothing visible. The bar's rows
count as off screen now.
- **`ls | gre` + Tab** completes `grep` rather than filenames. The
command-position test was "everything before the cursor is whitespace",
true only at the very start of the line; over SSH it also spent a round
trip listing the remote cwd to do it. Same for `a && b` and `a; b`.
- **Typing or pasting while scrolled back** snaps the view to the prompt, so
your own keystrokes are not landing off screen.
- **Typing into a filled text box** — rename, prompts — replaces the text
instead of prepending to it. Every one of them opens with its contents
selected.
- **Double-clicking a file over SFTP** opens it; only folders used to
respond.
- **Tab inside the palette** stays in the palette, and in the switcher
crosses to the tab column, instead of escaping to the next focusable
button.
- **Reveal in Finder on a remote workspace** is hidden rather than handing
the local file manager a path on another machine. Copy Path stays.
- **Things that failed silently now say so** — theme writes, theme
duplication, opening the themes folder, a server restart, a session restore
that came back short, an unparseable theme file, an SFTP overwrite, a search
count that hit its ceiling, a file-tree search that hit its ceiling, a delete
that did not go through, SSH auth with nothing to try, and an unknown action
in a keymap each either did nothing visible or blamed something else. All of
them now report what happened and, where there is one, what to do. The
sidebar's diff count no longer disagrees with the diff it opens, a
filtered-out SFTP directory is no longer called empty, and the theme picker
says when its filter matched nothing.
- **A directory with no children says which kind of nothing it is** — the
listing error was discarded, so a folder the OS refused rendered
byte-identically to one that really is empty and to one still being read. An
expanded directory now draws Reading…, Empty, Only hidden files, or Could not
be read; arrow-key navigation skips those placeholders because they are not
files. The tree column and the tab sidebar get the same treatment when a
filter drops everything.
- **Layout and hit targets** — the settings pages, the diff overlay, the
Markdown preview, the SFTP transfers list and the switcher's columns all
scrolled with no scrollbar and now have the one the rest of the app has. The
split divider takes an 8px grab, the same as the panel edges. Icon-only
buttons across the chrome get a 24×24 target without changing how they look,
and their tooltips print the chord. The active tab and the New Tab button
stay on the tab strip instead of scrolling out of reach. The switcher card
fits inside the window it floats over, and the window itself has a minimum
size. Below about 1000px the SSH host form stacks each control onto its own
line — rows measure the width they will actually get — rather than squeezing
the label column toward a letter per line and pushing Connect and the 2FA
option off screen. Nothing moves above the breakpoint.
- **Contrast, colour and theming** — button labels, panel headings, tooltips,
menus, the command line and the splitters are drawn in the active preset's
own colours instead of ignoring it. Sidebar text, carets, hairlines and the
semantic inks that bypassed the contrast machinery are floored, and a
hairline is worth the same in every theme. Three faded states had two values
each, the app had two transition curves, and the theme card rounded unlike
the panel it sits in: one value, one duration and curve, one radius.
- **An agent's state is not carried by hue alone** — Working, Needs input and
Done were a hue on a nine-pixel dot with no shape, label or tooltip
anywhere, and the pair that decides whether you go and look is amber versus
green, which is the pair red-green colour vision separates worst. Needs input
draws as a ring rather than a filled dot, the tab avatar has a tooltip naming
the agent and its state in all three locales, and a brand disc that lands
within 1.25:1 of the window fill — Codex and Grok both ship a pure black one,
which dissolves on a dark theme and leaves a white glyph floating — gets a
hairline.
- **The palette stops showing recent commands twice** — the zero-query list
pushed a Recent section and then pushed every group containing those same
commands, so the five rows you use most were the five rows shown twice.
Promoting to Recent moves a command now rather than cloning it, and a group
left empty drops its header with it. Ranking also threw frecency away the
moment a character was typed, so the command someone runs every day stopped
floating exactly when they started reaching for it; a bounded bonus rides
along with the match score — enough to separate two comparable matches, not
enough to let a well-worn command outrank a plainly better one.
- **SFTP transfers stop writing over files without a word** — downloading the
same file twice overwrote the first copy and uploading into the folder on
screen overwrote whatever was already there, neither with any warning and
neither recoverable. A download lands as `name (2).ext` the way a browser
does, still in one click; an upload asks once, naming what it would replace.
The upload check reads the listing already on screen rather than making a
round trip to ask.
- **Escape no longer throws away an unsaved SSH connection** — editing a
profile and pressing Escape closed Settings and discarded the form. Whether
the form was dirty was already known — it is what enables Save — but was only
used to grey out a button. Escape, the close button and ⌘, ask now, with Keep
Editing as the default. The nineteen rows of SSH Advanced also carry six
quiet headings — Authentication, Proxies, Algorithms, Connection, Session,
Security — of two to four rows each; no row moved, the existing order already
grouped this way and never said so.
- **Settings search marks what it counted** and scrolls the page to the hit,
including when the hit is a section header. Background images, Compression
and four other settings that were missing from the index are in it, and a
query that matches nothing on the current page no longer greys out a page you
are simply reading.
- **Copy, i18n and discoverability** — every failure opens with "Could not", as
most already did; every popup menu is in the Title Case the rest use; the
background server has one name everywhere; settings labels are sentence case;
and no Markdown backticks are printed at the user. The terminal's right-click
menu was the last surface still hardcoded English in a three-locale app, and
it also used a fourth name for actions the menu bar, palette and Keybindings
already agree on. Chinese quotes names the way Chinese quotes them, Japanese
descriptions end the way the other eighty do, the 16 ANSI colours are named
rather than numbered, and the remote Files search box re-translates on a
language switch. The i18n parity test was a hand-maintained 900-line copy of
the key list and now sweeps a generated one, with a documented allow-list for
the keys that stay English — and the untranslated-string guard actually
guards. Completion specs got git's missing descriptions filled in, cargo's
`--color` described like the other 41, and a rustup flag that described
itself fixed; a candidate says when its description was cut. The Keybindings
page is grouped by command group, SSH is reachable from the menu bar, "Enter
Full Screen" no longer appears twice, ⌘W names the command it is about to
end, and the View menu teaches the right key for Zoom Pane.
- **Switching workspaces no longer wipes the tab titles of the one you left** —
in the switcher, the workspace the window was showing had properly named tabs
while every other one read "Claude Code", over and over, for as many tabs as
@@ -3146,7 +3671,17 @@ Initial release.
- zsh shell integration (OSC 7 cwd + OSC 133 prompt marks) via a throwaway `ZDOTDIR`.
- Native macOS light/dark themes that follow the system appearance.
[Unreleased]: https://github.com/l0ng-ai/tty7/compare/v26.7.6...HEAD
[26.8.3]: https://github.com/l0ng-ai/tty7/compare/v26.8.2...v26.8.3
[26.8.2]: https://github.com/l0ng-ai/tty7/compare/v26.8.1...v26.8.2
[26.8.1]: https://github.com/l0ng-ai/tty7/compare/v26.8.0...v26.8.1
[26.8.0]: https://github.com/l0ng-ai/tty7/compare/v26.7.6...v26.8.0
[26.7.6]: https://github.com/l0ng-ai/tty7/compare/v26.7.5...v26.7.6
[26.7.5]: https://github.com/l0ng-ai/tty7/compare/v26.7.4...v26.7.5
[26.7.4]: https://github.com/l0ng-ai/tty7/compare/v26.7.3...v26.7.4
[26.7.3]: https://github.com/l0ng-ai/tty7/compare/v26.7.2...v26.7.3
[26.7.2]: https://github.com/l0ng-ai/tty7/compare/v26.7.1...v26.7.2
[26.7.1]: https://github.com/l0ng-ai/tty7/compare/v26.7.0...v26.7.1
[26.7.0]: https://github.com/l0ng-ai/tty7/compare/v0.17.0...v26.7.0
[0.10.0]: https://github.com/l0ng-ai/tty7/compare/v0.9.0...v0.10.0
[0.9.0]: https://github.com/l0ng-ai/tty7/compare/v0.8.0...v0.9.0
[0.8.0]: https://github.com/l0ng-ai/tty7/compare/v0.7.0...v0.8.0
Generated
+4 -4
View File
@@ -9768,7 +9768,7 @@ dependencies = [
[[package]]
name = "tty7"
version = "26.8.2"
version = "26.8.3"
dependencies = [
"alacritty_terminal",
"anyhow",
@@ -9812,7 +9812,7 @@ dependencies = [
[[package]]
name = "tty7-cli"
version = "26.8.2"
version = "26.8.3"
dependencies = [
"alacritty_terminal",
"anyhow",
@@ -9827,7 +9827,7 @@ dependencies = [
[[package]]
name = "tty7-core"
version = "26.8.2"
version = "26.8.3"
dependencies = [
"anyhow",
"base64",
@@ -9860,7 +9860,7 @@ dependencies = [
[[package]]
name = "tty7-server"
version = "26.8.2"
version = "26.8.3"
dependencies = [
"tempfile",
"tty7-core",
+1 -1
View File
@@ -260,7 +260,7 @@ edition = "2024"
# they must never drift apart. This is the line a release bump edits (and the
# one nightly's `awk '/^version = /'` stamps: it is the first such line in the
# file, since the package entries above are all `version.workspace = true`).
version = "26.8.2"
version = "26.8.3"
[workspace.dependencies]
# Our fork's `tty7` branch carries the local customizations tty7 relies on: