Files
tty7/CHANGELOG.md
T
l0ng-aiandl0ng-ai f660ddb1cc fix(ui): safe delete prompt default, platform-aware reveal labels, and searchable agent settings (#255)
* fix(ui): make Cancel the default button on the file-tree delete confirmation

The file tree's delete prompt was the only destructive prompt in tty7 with
the destructive action first. On macOS (NSAlert) and Windows (TaskDialog)
the first button is the Return-key default, so pressing Return deleted -
including recursive folder deletion. Linux uses gpui's fallback renderer,
which is click-only, so the swap only reorders the buttons there.

Safe option first, matching every other destructive prompt; the literal
"Cancel" is what gpui maps to PromptButton::Cancel and the Escape key.

* fix(ui): stop saying "Finder" on Linux and Windows

The file-tree context menu and the SFTP job tooltip hardcoded
Finder-flavoured labels; only the right panel's Info row was
platform-conditional. Extract that conditional into
right_panel::reveal_label() and use it at all three sites, so the action
reads "Reveal in Finder" on macOS and "Open Folder" elsewhere.

Side effect of sharing the helper: the SFTP tooltip's "Show in Finder"
becomes "Reveal in Finder" on macOS, converging a third name for the
same action.

* fix(settings): index the Grok Build agent row and re-align drifted index titles

The Grok Build agent renders a settings row but had no search-index
entry, so the row was unreachable by search. The other five agent
entries carried mechanism suffixes ("Claude Code hooks", "Pi
extension") that no longer match the rendered rows, which are titled by
HookAgent::display_name(); the index said "Option acts as Meta" where
the row says "Option (⌥) acts as Meta".

Align the titles with the rows, keep the mechanism words as search
keywords, and extend the index tests: the pinned-title list gains the
Option row, and a new test derives the Agents entries from
HookAgent::ALL so adding or renaming an agent without updating the index
fails the suite.

* no-mistakes(review): add plain search keywords for Option-acts-as-Meta entry

* no-mistakes(document): document copy fixes in changelog and apply rustfmt

---------

Co-authored-by: l0ng-ai <24760907+l0ng-ai@users.noreply.github.com>
2026-07-29 16:19:43 +08:00

1440 lines
84 KiB
Markdown
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.
# Changelog
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]
### Added
- **Pi is a first-class agent, not a fallback one** — Pi panes drew the generic
robot glyph every unbranded agent shares, so a Pi tab was indistinguishable
from an Aider or Qwen one in the sidebar, the tab chip and the tray menu. They
now carry their own avatar on the existing sky accent, status dot unchanged.
The mark is Pi's own, from pi.dev, rescaled to tty7's 24x24 icon grid — its
`prefers-color-scheme` stylesheet dropped, since these avatars are tinted by
the app. Restoring a Pi pane also resumes its conversation now: the tty7
extension reports Pi's session id, and the resume command is
`pi --session <id>` (Pi's `--resume` is a boolean that only opens the
interactive picker), with `--session` / `--session-id` / `--fork` /
`--resume` / `-r` / `--continue` / `-c` stripped off the replayed launch
flags so the restored id wins. A pane launched with `--no-session` is not
resumed at all — that pane never wrote a session to disk, and reopening one
would override the choice to keep it ephemeral. (#225)
- **Fork an agent session** — branch a live agent conversation into a second,
independent one, so a risky direction can be tried without losing the thread
that got you there. tty7 shells the agent's *own* fork command rather than
touching its transcript files: `codex fork <id>`, `claude --resume <id>
--fork-session`, `opencode --session <id> --fork`, `grok --resume <id>
--fork-session` — every one checked against the installed CLI's own help.
Agents with no fork tty7 could verify simply don't offer the action, rather
than getting a row that can only produce a usage error. The command carries
the pane's original launch flags exactly as session restore does, and sheds
the stale session-targeting ones so a fork of a fork can't branch twice or
replay an old id as a prompt.
Where the fork lands follows where you asked from: right-click a **pane** and
it asks for a split placement (Right / Left / Down / Up), since a pane-level
ask is a spatial one; right-click a **tab** or a sidebar row and it opens in a
new tab, with no placement question. The command palette and the File menu
carry the new-tab form only — a placement question means nothing when the ask
wasn't a spatial one — while all five, the four directions included, are
bindable in Settings → Keybindings.
A fork needs the session id the agent's hooks report, so the row disables
itself — rather than disappearing — until one arrives, and a remote pane can't
fork at all (the command would run against the *local* agent). Forking while a
turn is in flight is allowed but says so: agents fork from the persisted
transcript, so the turn you're watching won't be in the copy. The parent is
never modified either way. (#211)
- **Copy Session ID** — the agent's native session id on the clipboard, beside
*Copy Working Directory* in the tab / sidebar context menu, the palette and
the File menu. Codex has no copy-or-duplicate subcommand — forking *is* how
you duplicate a conversation there — so copying the id is what "copy the
session" means: paste it into `codex resume`, a bug report, or another tool.
(#211)
### Changed
- **The prompt editor's soft newline is now a rebindable action** — `Shift+Enter`
and `Alt+Enter` have inserted a literal newline into the command editor since
the multi-line prompt editor landed, but the chords were hardcoded in the key
handler: there was no `InsertNewline` to name in `keybindings`, and no way to
move the gesture to a chord of your own. The behaviour is unchanged out of the
box — both chords still insert, plain `Enter` still submits the whole buffer —
but it now runs through an `InsertNewline` action, so it appears in Settings →
Keybindings and can be rebound like anything else, and rebinding it retires
both defaults. Only the prompt editor answers it; with a full-screen program on
the pane the chord reaches the application exactly as before. `⌘⏎` fullscreen
and `⌘⇧⏎` pane zoom are untouched.
Two smaller behaviour changes come with it, both aligning on what other
terminals do. With a completion menu open, the newline chords now insert a
newline and close the menu instead of accepting the highlighted candidate —
plain `Enter` remains the key that accepts it. And `Shift+Alt+Enter`, which
the old modifier test caught by accident, now submits like any other `Enter`:
keybindings match modifiers exactly, and no terminal treats that three-key
chord as a newline. (#182)
- **Every header in the window moves it now** — grabbing the window by a header
is a property of the whole app rather than a per-surface feature, so you never
have to learn which rows happen to be draggable. The detail panel's section
title joins the caption rows that already were, wherever the panel draws one —
every tab off macOS, where that row is also the panel's tab switcher, and the
remote Files header on macOS. In horizontal-tab mode the strip also keeps a
bare 80px slice of caption for grabbing: its spacer was a flexible one with no
minimum, so it collapsed to exactly 0px once the tab chips saturated the row —
around 7-8 tabs on a 1440px window — leaving nothing to grab but three 6px
gaps and a hairline above and below the chips. The chip row's fixed-chrome
reserve is corrected to match: it was a flat 100px, sized when the corner held
a 30px "+" and a 30px "⋯", and was never raised when the workspace chip
absorbed the "⋯" menu in #169/#188 — so the row's width budget was ~20px of a
lie. It now measures the group it is reserving for, ~121px of fixed chrome plus
the 80px grab handle, so chips reach their minimum width and truncate a tab or
two sooner. (#221)
### Fixed
- **Rounded UI controls no longer square off their corners**
([#236](https://github.com/l0ng-ai/tty7/issues/236)) — the cursor-shape
toggles (Block / Bar / Underline) are the clearest case: the selected
segment's fill filled the whole corner of the track it caps, with the track's
own anti-aliased border arc floating *inside* that square. The controls were
relying on `overflow_hidden` to shape those fills to the track's rounding, and
it cannot: gpui's overflow mask is an axis-aligned rectangle with no corner
radii, tested per fragment as a hard discard, so it can only ever cut a square
and never anti-aliases the cut. A container's own corners come from the quad
shader's distance field instead, which is why a plain card looked smooth while
anything with a filled child in the corner did not. Every such fill now
carries its own radius, inset one border-width so it nests inside the border
rather than bulging past it: the segmented controls, the /value/+ steppers'
hover fills, the theme picker's flush previews, and the diff overlay's card
headers and closing rows. Most visible at a device pixel ratio of 1, where the
hard clip edge is a whole physical pixel wide.
- **Dragging the window by a title bar worked only rarely with a trackpad** —
five rows that stand in for the caption (the tab rail's top zone, the settings
page's top strip, the detail panel's top zone, and the code and diff overlays'
headers) armed their window-drag with a flag that was rebuilt on every render.
Any repaint between the press and the first drag event threw the arm away, and
the press *itself* schedules one — these rows carry a double-click, which makes
gpui refresh the window on mouse-down — so the drag only survived if the first
move beat the next vsync: 16ms at 60Hz, 8ms on ProMotion. A mouse press nudges
the pointer and often won that race; a trackpad press is a finger pushing down
without translating, and almost never did. The terminal's cursor blink disarmed
it on its own even without a press. The arm now lives in element state, which
survives frames — where gpui-component's own `TitleBar` has always kept it,
which is why the ordinary caption strip was never affected. (#221)
- **An idle window stays idle while a file in the Files panel is being written**
— the tree watches its roots plus every directory you have expanded, so what
it hears about is a change in a directory it is *displaying*, and a file's
contents being rewritten reports exactly as loudly as a file appearing. A
formatter rewriting in place, an editor saving on every keystroke, a build
dropping its log next to the sources: each of those repainted the whole
window, twice over, for as long as it went on. A window with nothing new to
draw never reached render idle, which is what issue #243 reports in its title.
A batch now repaints only when the re-read comes back different from what is
already on screen, and that re-read is issued from the watcher callback rather
than by asking for a paint in order to get one. A closed Files panel does no
work at all: the change is recorded and picked up on reopening. A panel showing
search hits is the same case — the hits are their own walk, so no listing is on
screen to refresh — and the change is picked up when you clear the search box.
Real changes still arrive exactly as fast.
Measured headlessly by counting window draws, before and after: five rewrites
of a file in a displayed directory cost 10 frames and now cost 0.
`.gitignore` edits keep their whole-cache refresh, now taken only when the file
can actually govern a directory the tree holds. That one is a correctness guard
on the most expensive branch here rather than a measurable saving — the watch
is non-recursive, so a `.gitignore` has to sit directly in a displayed
directory to arrive in the first place.
The other half of that report — the Files panel flickering — was fixed
independently by "keep file-tree listings on screen while they refresh", which
landed first and is the mechanism the panel now uses. This is only the frames.
Note the redraw behaviour is platform-independent and is what that number is
about; the reporter's ~15% CPU figure, and whether their flicker also
involved something in the Wayland presentation path, were not reproducible on
macOS and are not claimed to be confirmed. (#243)
- **Return no longer confirms the file tree's delete prompt** — it was the only
destructive prompt in tty7 with the destructive action first, and on macOS
(NSAlert) and Windows (TaskDialog) the first button is the Return-key
default, so pressing Return deleted — recursive folder deletion included. The
buttons now put the safe option first, matching every other destructive
prompt, with Escape still cancelling. Linux uses gpui's click-only fallback
dialog, so the swap only reorders the buttons there.
- **"Finder" is no longer named on Linux and Windows** — the file-tree context
menu and the SFTP job list's reveal tooltip hardcoded Finder-flavoured
labels on every platform; only the Info row's button was conditional. All
three sites now share that one conditional, so the action reads "Reveal in
Finder" on macOS and "Open Folder" everywhere else. On macOS the SFTP
tooltip's "Show in Finder" becomes "Reveal in Finder" too, retiring a third
name for the same action.
- **Grok Build turns up in settings search** — the agent renders a Settings →
Agents row but had no search-index entry, so searching could never surface
it. The other five agent entries had drifted from what their rows actually
say ("Claude Code hooks" for a row titled "Claude Code"), as had "Option
acts as Meta" from the rendered "Option (⌥) acts as Meta"; the index titles
now match the rows, the mechanism words (hooks / plugin / extension) stay
behind as search keywords, and a test derives the Agents index from the
agent list so a future agent can't ship unsearchable.
## [26.7.6] - 2026-07-28
### Added
- **Readline parity at the prompt** — keys that worked in a raw terminal stopped
working the moment shell integration engaged, because the local command editor
owned the keyboard at the prompt and swallowed every chord it didn't
recognise. `Ctrl-P` / `Ctrl-N` now walk history exactly like ↑ / ↓ (completion
picker and reverse search included); `Ctrl-Y` yanks back whatever `Ctrl-W` /
`Ctrl-U` / `Ctrl-K` / `Alt-D` last cut, from a one-slot kill ring of the
editor's own — zle's ring holds text this editor never cut, so borrowing it
would paste the wrong thing; and `Alt-.` inserts the previous command's last
word, repeating to walk further back. Any chord the editor has no answer for
`Ctrl-T`, `Alt-U`, a `bindkey`-ed widget — now hands the line to the shell
so zle's keymap decides, instead of vanishing. Pressing any of them while
scrolled up snaps the viewport back to the live prompt first, so a recalled
line can't be edited off-screen. (#222)
- **One Dark Pro built-in theme** — a ninth built-in, slotted alphabetically
among the dark themes. Two deliberate departures from the VS Code theme's
terminal set, both caught by this repo's contrast tests: the accent is the
editor's focus blue `#528bff` rather than the syntax blue, which sits at the
same luminance as the switch knob it has to be legible against; and normal red
stays the classic `#e06c75`, because the Pro terminal red lands too close to
the warning orange to stay distinguishable. (#224)
- **Panes are told which terminal they're running in** — every pane now carries
`TERM_PROGRAM=tty7` and `TERM_PROGRAM_VERSION`, the de-facto standard pair
Apple Terminal introduced and iTerm2, WezTerm, Ghostty, VS Code and tmux all
set. `TERM` names terminfo capabilities and can't answer "which program is
this", so without the pair, capability probes (`supports-color`,
`supports-hyperlinks`, and the CLI ecosystem built on them), editors applying
terminal-specific workarounds, and shell prompts all fell back to their most
conservative behaviour. tty7's own `TTY7` marker doesn't help them — it exists
so globally-installed agent hooks stay silent in other terminals, and nothing
third-party knows to look for it. Unlike `TERM` and `COLORTERM`, both new
variables can be overridden from `env` in `config.json`: they name an
identity, not a capability, and posing as another terminal is a legitimate way
to get a tool that only recognises a fixed list to light up. Local panes only
— ssh forwards environment variables solely by agreement between client and
server, so a remote host still sees whatever it sets for itself. (#219)
- **Inactive panes only fade if you want them to** — a split tab dims every pane
but the focused one so the active terminal reads as foreground. That is the
right default, but it is not free: at 55% opacity a dim theme's comment color
or a long-running build's output in the pane you are *watching* rather than
typing into gets harder to read, and some people track panes by cursor alone
and never needed the cue. Settings → Appearance → Transparency now carries a
"Dim inactive panes" switch. On by default, so nothing changes for anyone who
was happy; off renders every pane at full opacity. (#214)
### Changed
- **One fewer duplicate SVG stack in the build** — the resvg 0.47 bump (#227)
left the gpui fork on 0.45, so the tree compiled two resvg/usvg/tiny-skia
stacks. The fork now pins 0.47 too, re-unifying its stack with the one tty7
uses for the tray icon. (#237)
- **The last duplicate SVG stack is gone** — after #227 and #237 unified tty7
and the gpui fork on resvg 0.47, the gpui-component fork still declared its
own `resvg = "0.45.1"`, keeping a legacy resvg/usvg/tiny-skia 0.45/0.11
stack in the tree. That fork now pins 0.47 as well, so the whole build
compiles a single resvg stack. (#238)
### Fixed
- **Box-drawing characters no longer break into dashes between rows** — they
rendered as font glyphs, and a glyph only covers the font's own line height
while the cell is `font_size × line_height` (1.4 by default). At any line
height above 1.0 every vertical run of `│` `╭` `╰` was perforated at each row
boundary: two-line prompts never closed their corners, TUI frames were dotted
down both sides. U+2500U+257F and U+2580U+259F are now drawn as native
geometry pinned to the cell's real edges — the same special case kitty,
Alacritty, WezTerm and iTerm2 all ship, and what tty7's Powerline separators
already did. Covers mixed light/heavy weights, the double-line set with its
junctions left open, rounded corners, dashes, diagonals, block eighths and the
░▒▓ shades. (#229)
- **Underlines survived box-drawing characters, and strokes stay one weight at
fractional DPI** — two things the native-drawing path changed without meaning
to, both invisible at the integer device scale it was developed on. An `ESC[4m`
span or a hovered URL showed a one-column hole wherever it crossed a box
character, because the underline rides the shaped text run and the native cell
returned before building one; the cell now shapes a space in its own style, so
gpui draws the line from the same `UnderlineStyle` as its neighbours. And at
125% / 150% / 175% display scaling a vertical rule alternated thin/thick across
every column, since a logical stroke width that isn't a whole number of device
pixels rounds differently per column; widths are now laid off the snapped near
edge in whole device pixels. Nothing changes at 1x or 2x. (#234)
- **Thai SARA AM lost its vowel and rendered as a dotted circle** — `ำ` (U+0E33)
is width 1 and correctly gets its own column, but it is not atomic to the
shaper: rustybuzz decomposes it and moves the nikhahit backwards onto the base
consonant, which needs the base in the same run. It was being segmented alone,
so there was nothing to reorder onto. A following SARA AM is now absorbed into
the preceding cell's cluster, so base, tone mark and vowel shape together. Lao
SARA AM (U+0EB3) takes the same path and comes along. (#230)
- **Italic CJK rendered as unrelated CJK on Windows** — every character came out
as a different character, one for one, consistently, so it read as a broken
locale or a mangled encoding. It was neither. Hack, the bundled default, has no
CJK, so those cells are shaped by the font-fallback chain; gpui's Windows
backend then threw away the face DirectWrite shaped with and looked a fresh one
up by family, weight and style. That round trip mapped DirectWrite's *italic*
to *oblique* — the two are numbered the other way around in the API — and a
family with no oblique face resolved to its upright one. The glyph indices were
right; the outlines they were pointing into belonged to a different face. Fixed
in our gpui fork by rasterizing the face DirectWrite actually chose, which also
closes a latent use-after-free in the same cache: it keyed fonts by a raw
pointer to a face nothing held a reference to. (#233)
## [26.7.5] - 2026-07-27
### Added
- **Tab completes remote paths in an SSH pane** — path candidates came off the
local filesystem, so a native-SSH pane deliberately passed no cwd and Tab
found nothing; a no-match hands the line to the shell, which costs the inline
editor until the next prompt. Since paths are what people complete most, in
practice every path Tab in an SSH session dropped the user back to the raw
shell. Tab now lists the remote directory over the pane's own authenticated
connection — the same request the SFTP panel browses with — so nothing is
echoed into the scrollback and no prompt hooks are involved. `cd` still filters
to directories. Command position and `~/` still fall through, as does a
foreground `ssh` typed into a local shell or a WSL pane, neither of which has a
tty7-owned connection to ask. (#217)
- **The last-window close confirmation can be turned off** — Settings → Window &
Tabs gains **Confirm before closing the last window**, on by default. The
prompt is a teaching device, not a safety net: ⌘Q, the tray's Quit and the
palette all quit without asking, and nothing is lost either way since the panes
keep running in the daemon. Once that model is learned, an extra dialog on
every quit is friction. Brings the window-close prompt in line with the SSH
close warning, which has had a toggle all along. (#206)
- **The detail panel and tab rail have a scrollbar** — Info / Outline / Changes,
the file tree, the remote SFTP listing and the tab rail all scrolled with a
container that painted nothing, so a deep tree or a long tab list gave no hint
that there was more content or where in it you were. The bar takes its colours
from the active theme rather than a stock grey, and follows the platform:
auto-hiding on macOS, permanently visible on Windows and Linux. (#193)
- **Foreground applications can negotiate the Kitty keyboard protocol** — the
embedded terminal config inherited `kitty_keyboard: false`, so the parser
ignored the negotiation sequences and an application asking for progressive
enhancement fell back to `modifyOtherKeys`, which tty7 doesn't implement. That
collapsed distinct chords onto the same legacy byte — `Shift+Enter` reached the
application as a plain carriage return, submitting a prompt instead of
inserting a soft newline. Legacy input is unchanged until an application opts
in. (#184)
- **The window's leading corner carries the app's mark off macOS** — macOS fills
the top-left with the traffic lights; on Windows and Linux that corner was
empty, with everything the caption row holds (the rail's "+" and collapse, the
corner chrome, the window controls) pushed to a right edge. The "duo" mark now
heads the tab rail on its content inset, the line the search box and every row
label below it start on, and follows the rail's controls into the title strip
when the sidebar is collapsed — so the corner never falls back to nothing.
Drawn, never clicked: it takes no hover capsule and no hit box, leaving the
strip grabbable through it. (#202)
### Changed
- **Interaction state and status colours are derived from the active theme**
([#197](https://github.com/l0ng-ai/tty7/issues/197)) — a segmented control's
selected option was indistinguishable from its neighbours on *every* bundled
theme (Dracula worst at 1.03:1), because the theme had a colour model but no
state model: fixed blend ratios scattered across the code, plus every
gpui-component field nobody had noticed still carrying stock greys and Tailwind
hues. Each painting surface now carries a state ladder derived to hit a
contrast *ratio* against that surface, so the result no longer drifts with the
theme — selected-vs-resting goes from 1.201.47:1 to 1.701.72:1, anchored so
Dracula's already-signed-off highlight is a no-op. Ladders are per surface, so
a menu row anchors to the popover it actually sits on. Taking a fill now also
takes its paired label colour, instead of each site hand-fixing that
separately. `danger`/`warning`/`success`/`info`/`link` come from the theme's
own ANSI-16 at 33 call sites — Dracula's delete button is literally the
`#ff5555` the terminal in the same window paints with. Switches were inverted
on every dark theme (a near-black knob on a light track, invisible on the dark
one) and now take the light end of the theme's axis with the accent on the
checked track. (#205)
- **The bundled icon set is redrawn to one humanist spec** — the previous set
accumulated one exception at a time: a heavier `plus` because a bare cross
looked frail, a `stock/` prefix to undo overrides that were too heavy at 16px,
two names sharing one drawing. All 17 glyphs now hold to a single spec — 2.1
stroke throughout, corner radii matching the app's 10px panels, near-square
boxes of equal apparent area, round caps and joins everywhere. Metaphors are
untouched, so nothing needs relearning; `folder-closed` gains an inner rule
that distinguishes it from `folder`, and `info` a title bar that stops it
colliding with `panel-left`. (#190)
- **The title bar spans the detail panel off macOS** — the panel was a
full-height column *beside* the bar, and the bar lays out ─ ▢ ✕ at its own
right end, so opening the panel on Windows or Linux stranded the window
controls mid-window with the panel's grey to their right. Layout moves to
`[rail | col(bar / row(body, panel))]` so the bar reaches the corner, with the
caption row over the panel painted in the panel's own surface — the column
reads as one continuous sidebar from the very top instead of starting 40px down
in a different colour. The panel's tab tiles move into the section header it
was already drawing, since a tile row of its own made three stacked headers
before a single line of content. macOS is untouched. (#188)
### Fixed
- **Splitting or un-maximizing a pane you were hovering no longer kills the app**
([#201](https://github.com/l0ng-ai/tty7/issues/201)) — a pane remembers the
cell under the pointer (that's what makes ⌘-hover underline links), and nothing
invalidated it when the grid shrank underneath. The remembered row then named a
line the grid no longer had, and the next modifier press indexed the grid with
it — inside a gpui input callback, which is `extern "C"` and cannot unwind, so
the process aborted with the message lost. `⌘⇧D`, `⌘⇧⏎` and dragging the window
smaller were all reliable ways to hit it. The hovered cell is dropped on resize
and the row is validated before indexing. Two other aborts went with it: `⌘T` /
`⌘D` against a dead daemon now restarts it and retries once, and session
restore with an unreachable daemon restores what it can instead of dying. A
panic anywhere in the GUI now also lands in `<config-dir>/crash.log` with its
message, location and backtrace — the OS report only ever kept the abort.
(#204)
- **Emoji written with a variation selector take their real width and
presentation** ([#203](https://github.com/l0ng-ai/tty7/issues/203)) — `🗂️`,
`❤️` and `⚠️` were budgeted one column, so the glyph bled over its neighbour
and every following cell on the line shifted left by one, taking selection and
click hit-testing with it. The selector is zero-width and lands *after* the
base's column budget is spent, and nothing revisited the decision. The same
gap made `❤️` render as the black text-presentation heart, identical to bare
`❤`, because only `cell.c` reached the shaper — dropping every combining mark,
not just variation selectors. The terminal now re-scores the sequence and
widens the cell, and carries the marks through to be shaped with their base:
`❤️` and `⚠️` come up in colour while their bare forms stay monochrome, and
`e` + U+0301 renders as `é`. Narrowing (`U+FE0E` on an already-wide emoji) is
still not implemented — it would have to free a column and reflow the line.
(#210, #216)
- **Untrusted terminal output can no longer freeze a pane** — with Kitty
keyboard negotiation enabled, an upstream `alacritty_terminal` bug became
reachable: `push_keyboard_mode` caps its stack by removing from the *title*
stack, a copy-paste slip that compiles because both are `Vec`s. With an empty
title stack that panics and kills the reader thread, freezing the pane; with a
non-empty one it silently drops a saved title, so a later restore returns the
wrong one. It doesn't take hostile output — a TUI that pushes without popping
reaches the 4096 cap on its own in a long session, and `cat` of a crafted file
or a remote host over SSH gets there in one ~20KB burst. Pinned to a patched
fork, with a regression test that guards the pin. (#194)
- **`theme_follow_system` no longer panics every launch on Linux** — flipping
"sync with system" on in Settings persists immediately, so the app then aborted
on every start until `config.json` was hand-edited back, with a backtrace
pointing at gpui internals and nothing connecting it to the theme toggle. gpui's
Wayland and X11 backends dispatch the appearance-changed callback while the
platform client's `RefCell` is already mutably borrowed, and reading
`cx.window_appearance()` from that callback re-borrows the same cell. The OS
appearance is now cached in a global that the observer fills from the window's
own cell, so nothing on the re-entrant path touches the client. macOS and
Windows were never affected. (#181)
- **A config file saved with a UTF-8 BOM no longer wipes your settings** — every
config-dir file is read by a loader that treats any parse error as "there is
no file", falling back to defaults. `serde_json` rejects the U+FEFF a BOM puts
before the opening brace, so a BOM didn't report a broken config — it reported
an absent one, and tty7 came up on defaults with nothing in the log to explain
it. Windows makes this easy to hit by accident: PowerShell's `>`, `Out-File`
and `Set-Content -Encoding utf8` all write a BOM, so editing `config.json`
from a shell was enough to lose every setting. `config.json`, `session.json`
(which lost every workspace the same way) and hand-authored `themes/*.yaml`
now skip a leading BOM. (#215)
- **New tabs and splits open in the right directory even when the shell can't be
instrumented** ([#187](https://github.com/l0ng-ai/tty7/issues/187)) — a pane
learned its directory from `OSC 7`, which only shells tty7 injects its
integration into ever emit. A shell that `exec`s into another one from its rc
file (`exec fish` at the end of `.zshrc`), a nested shell started by hand, or
any shell with no integration at all emitted none — and because a pane's
directory is seeded with the one it was spawned in, such a pane didn't report
*no* directory, it reported a permanently stale one. New tabs, splits, the git
probe and path completion all followed it to the wrong place, with nothing on
screen to say why. The pane now also reads its directory from the process
table, on the same half-second foreground poll that already detects SSH
sessions and coding agents. A shell that does emit `OSC 7` keeps its own
spelling of the path: `$PWD` preserves the symlinked route the user walked in
through, and that is the one a new tab should open in. macOS and Linux;
Windows has no equivalent process query and is unchanged. (#207)
- **Links open on Ctrl+click on Windows and Linux**
([#183](https://github.com/l0ng-ai/tty7/issues/183)) — the link modifier was
the platform key, which gpui maps to ⌘ on macOS but to Win/Super elsewhere, a
key the OS mostly swallows. Off macOS neither the hover underline nor
click-to-open could be triggered at all, and the only way to follow a URL was
to select and copy it — while `config.json`'s own docs had promised
"⌘/Ctrl-click" all along. Now the same portable secondary modifier the
keybindings already use, so ⌃-click keeps meaning right-click on macOS.
Settings copy and the docs follow the platform. (#192)
- **Option-as-Meta works with a CJK input source**
([#177](https://github.com/l0ng-ai/tty7/issues/177)) — macOS gives Option two
jobs, and routes the chord before the terminal sees it. With a non-ASCII input
source active, ⌥F went to the IME, which committed `ƒ` and consumed the event,
so the code that turns the chord into `ESC f` never got a say. The setting
worked on ASCII layouts and silently did nothing on CJK ones, which is why it
read as intermittent rather than broken. gpui can now decide per keystroke
rather than once per view, so Meta chords are claimed by the terminal while
ordinary text, dead keys and Pinyin composition still reach the IME. gpui
accordingly moves to the `l0ng-ai/zed` fork. (#191)
- **A whole command cycle arriving in one read reports both prompt states** —
the OSC sniffer folded a chunk's shell-integration marks into one state and
sent a single frame, hiding the prompt boundary the client counts to tell a
fresh prompt from a same-prompt redraw. Routine over SSH, where a fast
command's start mark, output and completion mark leave the remote in one
packet. Marks on the same side of a boundary still fold, so an ordinary prompt
draw costs one frame as before. (#217)
- **The detail-panel toggle stops drawing itself as selected, and the Files
dotfile switch moves into the tree's right-click menu** — follow-ups to the
caption-row rework, which left the toggle permanently lit and the dotfile
control competing for room on the header line. (#188)
- **The editor and diff overlays keep their header on the caption line when the
detail panel is open** — off macOS the title bar is hoisted above
`[terminal | panel]` so the ─ ▢ ✕ group can reach the window's corner, which
left both overlays — anchored to the terminal column — starting 40px down.
Their headers are drawn to *be* the title bar while they're up (its height,
its insets, a full chrome tile for their one control), and instead landed a
row low, level with the panel's tab row. They now hang on the row that owns
the bar, inset by the panel's width, so the corner chrome keeps its surface
and its clicks. (#202)
- **Those headers became real title bars** — dragging one now moves the window
and double-clicking zooms it. Both covered the caption row and neither did
either, with the panel open or closed, so opening a file turned the top of the
window into a 40px strip that looked exactly like a title bar and answered
nothing. Their controls (the ✕, the diff's back-to-all-files chip) are
`occlude()`d to keep taking clicks: a drag region on Windows is HTCAPTION, and
the OS claims the press before the app hit-tests. Double-clicking a stand-in
title bar zooms the window on Linux too. (#202)
- **The rail's top zone lines up with the title bar to the pixel** — the bar
reserves a hairline inside its own height that the rail's stand-in row didn't,
so everything in that row sat half a pixel low. Invisible on the line-art
tiles; not on the mark, which visibly hopped as collapsing the rail handed it
over to the bar. (#202)
- **CJK and emoji stop falling through to the OS on Windows and Linux** — the
default `font_fallbacks` named only faces that ship with macOS (Menlo, Apple
Color Emoji), so off macOS the entire chain matched nothing and every
ideograph and pictograph was resolved by the platform's own cascade instead.
The bundled Hack primary carries no CJK at all, so on Windows this was every
Chinese character in every pane. Defaults are now chosen per platform —
PingFang SC / Apple Color Emoji on macOS, Microsoft YaHei / Segoe UI Emoji on
Windows, Noto on Linux — and those stock names are appended to a
hand-written list as well, so a `config.json` that predates this change (or
was copied from another machine) is repaired at use time without being
rewritten. Maple Mono NF CN stays first on every platform: its 1.2em CJK
advance is the only exact fit for the two-column slot Hack's 0.60205em cell
produces, so a 1.0em stock face is left-aligned there with the remaining
~0.2em showing as a gap on the right of each character. (#195)
## [26.7.4] - 2026-07-26
### Added
- **Grok Build gets the full hook integration** — Settings → Agents grows a
sixth row, installing tty7's hooks to `~/.grok/hooks/tty7.json`. A grok pane
now carries the same live status the other agents do (blue "working" → green
"done", amber "needs you" when grok asks a question), and after a restart it
relaunches `grok --resume <id>` with the original launch flags instead of a
bare shell. Panes that only ever had the Claude Code hooks installed are
relabeled to grok rather than reporting as "Claude Code", and grok's brand
mark replaces the generic robot avatar. (#174)
- **Edit, File and Help menus** — copy/cut/paste/select-all, undo/redo and
find/find-next now live in a real Edit menu; `About tty7`, `Check for
Updates…`, `Hide tty7` and Services sit in the app menu; Docs, Discord and
`Report an Issue` are reachable from Help; and tab actions that used to exist
only in a context menu (Rename Tab, New Worktree Tab, Close Other Tabs, Close
Tabs to the Right, Copy Working Directory) are real actions you can also find
in the palette and rebind in Settings. (#175)
### Changed
- **The menu bar follows the HIG** — `tty7 · File · Edit · View · Window ·
Help` replaces `tty7 · Shell · Window · View`. The `Shell` menu is gone: its
new / close / split / rename items are File's job everywhere else, and the
name collided with Settings → Shell, which configures something entirely
different. View gained the layout toggles and pane commands that were
chord-only, and `Delete Workspace…` moved behind its own rule at the bottom
of File. (#175)
- **The command palette is ranked, banded, and says what it will do** — results
are scored rather than filtered, so word-initials and prefixes outrank
scattered letters; the idle list opens on `Recent` (frecency) followed by
Tabs & Panes · Workspaces · View · Terminal · SSH · Agents · Application
instead of 47 flat rows. Names describe the outcome and the current state
(`Hide Left Sidebar`, `Tab Bar: Move to Left Sidebar`) rather than the switch,
one namespace prefix per subsystem, and the naming grammar is written down at
the top of `palette.rs`. (#175)
- **Settings re-sectioned around what you're configuring** — the orphan `Shell`
page folds into Terminal's first group, a new `Input` page collects the prompt
editor, selection/clipboard and keyboard settings, Terminal drops from seven
groups to four, and notifications moved to Window & Tabs where the rest of the
app-level behaviour lives. The search index grew from 14 entries to 51 with
titles pinned to the rendered row labels, so `opacity`, `blur`, `completion`,
`ctrl-r` and `grouping` now find their rows. (#175)
- **The tab bar defaults to the left sidebar** — a fresh install opens with the
vertical tab rail instead of the horizontal title-bar strip. Anyone who has
flipped the setting or hit `ToggleTabSidebar` has an explicit value persisted
and keeps their layout. (#171)
- **Idle chrome tiles paint at the rail's ink weight** — title-bar toggles,
panel tabs, `+` and their siblings now draw their glyph in
`sidebar_foreground` instead of the near-black `foreground`, with full
strength reserved for the selected tile. The chrome no longer reads a step
darker than everything it neighbours, and "on" gets a second cue beyond the
grey capsule. (#176)
### Fixed
- **Non-ASCII output survives a Finder/Dock launch** — macOS GUI launches can
omit every locale variable, leaving the shell in the C locale: `ls` printed
one `?` per non-ASCII byte, so a CJK filename came out as `??????`, and tmux
replaced each Unicode cell with `_`. New shells now receive a UTF-8 character
locale (`LC_CTYPE` only, so message/date/number localization is untouched)
derived from the system locale the way Terminal.app does it, and only when no
inherited or configured locale exists — explicit locale choices still win.
The derived name is checked against the locales actually installed before it
is exported, so it stays loadable on remote hosts too, where ssh forwards
`LC_*` by default.
([#178](https://github.com/l0ng-ai/tty7/issues/178), #173, #180)
- **Right-click Paste dropped images, and Copy looked disabled** — copy, cut,
paste and undo each had two implementations, a chord handler and an action
handler, which had drifted: the context menu's Paste skipped the image branch
⌘V had, so pasting a screenshot into an agent pane worked by keyboard but not
by menu, and Copy rendered disabled whenever the selection lived in the prompt
editor rather than the terminal grid. Both now route through one method each.
(#175)
- **The palette named the other window's sidebar** — the stateful palette titles
read the config copies of `sidebar_collapsed` / `right_panel_visible`, which
only record whichever window toggled them last. With two windows in different
states, one window's palette offered "Show Left Sidebar" while its rail was
already out. Each window now labels the toggles from its own chrome state.
(#175)
- **No more update prompts for a half-built release** — the release step ran
inside the build matrix, so the first platform to finish published a release
carrying only its own assets, and that release became `/releases/latest`
immediately. The in-app update check polls exactly that endpoint, so users
were prompted to download a version whose `.dmg` was still notarizing. All
four platforms now upload artifacts and a single gated job assembles one
**draft** release with every asset. (#172)
## [26.7.3] - 2026-07-25
### Added
- **Multiple windows, one workspace each** — `New Workspace` (⌘⇧N) opens a
second window with its own tabs, splits, and chrome state. A workspace is
the persistent thing: closing its window puts it away with its panes still
running in the daemon, and the title bar's workspace menu, the command
palette, and the home page's picker all bring it back. `Stop Workspace`
ends one for real (no default chord — it kills sessions), `Delete
Workspace` also forgets the layout, and a workspace can be renamed from
the title-bar chip. (#169)
- **Workspace switcher, in the two places you'd look** — a title-bar chip
(monogram + chevron) whose menu lists every workspace with a monogram badge
and a corner dot for the ones whose shells are still running, and the macOS
**Window** menu listing the same set: on screen first, then the detached ones
with how long ago you left them. Both show the first nine. (#169)
- **Detail panel** — a docked right-hand column that shows what the active
pane *is* rather than what it's printing: **Info** (cwd, branch, shell,
agent, the process tree and the TCP ports the pane is listening on),
**Outline**, **Changes**, and **Files** (a local file tree). Opening a file
from it lifts a code editor overlay over the terminal. (#158)
- **Port forwarding and SFTP live in the detail panel** — a native-SSH pane's
forwards are listed, added and removed from the Info tab, and its remote
filesystem browses from the Files tab, with transfers reported in a footer
that rides under every tab. (#166)
- **Shell integration in native SSH panes** — OSC 133 prompt marks, exit
codes and cwd reporting now reach zsh, bash and fish on the far side, so
the inline line editor works in an SSH pane exactly as it does locally.
(#152)
- **Live drag-to-reorder** — tabs rearrange under the cursor the way Chrome
and Warp do (the dragged tab stays in the list and the ones it passes slide
out of the way) in the strip, the sidebar rows, and whole repo groups.
(#151)
- **The rail's top strip drags the window** — the sidebar's title-bar-height
top zone now moves the window on drag and zooms it on double-click, like
the title bar it sits level with. (#153)
- **The ⌃R history menu can be switched off** — Settings → Terminal →
Keyboard, or `history_search` in `config.json`. With it off, the prompt
line is handed to the shell and the raw `^R` follows it, so a binding
of your own (fzf, percol, plain reverse-i-search) answers instead of
tty7's menu. (#163, #170)
### Changed
- **Sidebar and right-panel visibility are per-window** — toggling one
window's chrome leaves the others alone. The config value is now what a
*newly opened* window starts with; panel width stays shared. (#169)
- **Chrome icons draw at the size they were asked to** — every tile glyph in
the window (title bar, rail, panel tabs, overlay headers) had been rendering
at 12px regardless of the size its call site set, because the button widget
overwrites its icon's size from its own. The tile rhythm is now stated once
and applied from one helper, so the marks read at their intended weight and
the hover capsule keeps a consistent gap from the window edge. (#169)
- **The panel and chrome glyphs are drawn to one spec** — the detail panel's
tab marks and the title bar's own were redrawn so a row of icons stops
reading as a row of icons from different sets, and every icon tile shares
one soft hover fill. (#161)
- **Multi-line commands submit as one bracketed paste** — a 30-line block
costs one prompt cycle instead of thirty, so it no longer retypes itself
down the screen on Enter. (#164)
- **The file tree lists directories off the render thread** — `read_dir`, the
`.gitignore` chain and the search walk left the paint, so a cold cache or a
large repo no longer stalls a frame. (#160)
- **The LSP client is gone** — opening a `.rs` file in the code panel silently
spawned rust-analyzer, which indexed the whole workspace for hundreds of
megabytes of RAM. A terminal shouldn't do that to you on a click, and the
fix is removal rather than a switch for something nobody asked for. (#159)
- **Sidebar rows float their close button** — the ✕ no longer reserves a
permanent trailing column, so labels and branch lines get the full rail
width, and it stays clear of a row's `+n n` counts. (#145)
- **Daemon wire protocol is now v2** — WSL panes carry a remote-context kind
that a v1 client can't decode, so it would drop the pane's connection
instead of ignoring the unknown value. The version handshake now sees that
skew and offers to restart the daemon, rather than letting a downgraded
build lose panes silently. (#169)
- **`Cargo.lock` is checked against `Cargo.toml` in CI** — release builds now
run `--locked`, so a drifted lockfile fails the build instead of quietly
resolving something else. (#150)
### Fixed
- **⌃J and ⌃M submit the line again** — accept-line's control codes were
swallowed at the prompt as unrecognized Ctrl chords, so the keys did
nothing. They now take the same path Enter does, completion picker and
history menu included. (#163, #170)
- **The sidebar's git counts keep up** — `+N N` refreshes as an agent's tool
calls land (throttled per repo) and when the window regains focus, instead
of going stale until you happened to run a command in that pane. (#149)
- **`ToggleSftp` earns its name** — pressing it while the panel is already on
Files closes the panel rather than being a dead press, and the remote
browser's 500ms transfer poll now ends itself when the panel closes instead
of depending on the render path to retire it. (#167)
- **Closing the last window from the home page quits** — it used to leave a
windowless process in the Dock that no longer responded to being clicked.
(#147, #148)
- **Windows titlebar chrome** — the `` menu sits back on the native
window-control rhythm, and the sidebar's collapse / new-tab buttons take
their clicks again instead of being swallowed by the drag region. (#162)
- **Windows: the theme panel's close button did nothing** — and the repo group
header showed a plain arrow where every other row shows a pointer. (#155,
#156)
- **Settings keeps its stock glyphs** — the page's own icons stopped being
restyled by the chrome tile pass, and its close button is sized to the title
bar's rhythm rather than reading undersized beside the window controls.
(#157, #165)
## [26.7.2] - 2026-07-21
### Added
- **WSL shell integration** — prompt marks, cwd tracking, and agent
detection now work inside WSL distros. The integration tags the shell
it actually launches, never blocks the spawn path while probing the
distro's shell, and declines untranslatable cwds rather than
misreporting them. (#134)
- **Git Bash shell integration on Windows** — Git Bash panes get the
full integration, reporting proper Windows paths for cwd-dependent
features. (#130)
- **Smart double-click selection** — double-click selects the word under
the cursor with language-aware boundaries, including CJK segmentation
(the OS tokenizer on macOS, jieba elsewhere); angle-bracket pairs must
hug their contents and contraction apostrophes stay out of quote
pairing. (#128)
- **Configurable file-link command** — file links can open with a custom
command instead of the default editor. Contributed by @ayamir. (#143)
- **Agent resume keeps launch flags** — resuming an agent session
carries the flags the agent was originally launched with onto the
resume command. (#144)
- **Sidebar git line follows the agent's cwd** — the branch/status line
under an agent tab tracks the directory the agent is actually working
in, not just the pane's shell. (#127)
### Changed
- **Tab close button floats over the label** — the close affordance no
longer reserves a slot in every tab, so labels get the full width
until hovered. (#126)
- **Dependency updates** — resvg 0.47, sha2 0.11, a cargo minor-patch
group bump, and newer CI artifact actions. (#137, #138, #139, #140,
#141)
### Fixed
- **Tab completion falls through to the shell** — a Tab tty7 has no
candidates for now hands the line to the shell's own completion
instead of being swallowed (remote panes included); `cd`/`pushd` stop
offering files; and the whole feature can be turned off with
`tab_completion` in config or Settings → Terminal → Keyboard. (#146)
- **Remote panes never leak local filesystem operations** — a remote
pane's cwd is no longer used for local path lookups, and the agent-cwd
bypass validates inherited directories. (#133)
- **macOS text input goes through the IME** — synthesized keystrokes
keep their text (remote/automated input no longer degrades), and Kitty
full mode stays off the IME path. (#132)
- **No more console flashes on Windows git probes** — git status probes
and ssh ProxyCommand children no longer flash console windows. (#129)
- **Windows titlebar menu spacing** — the titlebar "..." aligns with the
window-control rhythm. (#131)
- **Codex avatar and macOS tray sizing** — the Codex tab avatar uses its
black brand field, and the tray icon renders at 22pt instead of the
hardcoded 18pt. (#142)
- **Agents settings layout** — rows stay aligned and notes terse. (#125)
## [26.7.1] - 2026-07-17
### Added
- **Follow the OS appearance** — a "Sync with system" mode with separate
light and dark theme slots: the theme flips live when the OS switches
appearance, native chrome follows along, and picking a theme while
syncing writes the slot matching the current mode. Old configs are
unchanged (sync defaults off). (#121)
- **Mark as Unread on tabs** — the tab context menu can re-flag a finished
agent result you've already looked at, re-arming the unread badge on the
Done dot. Agent tabs only; disabled while the agent is still working.
(#120)
### Changed
- **"Duo" logo refresh** — a new brand mark (two offset session panes,
mint behind ink, with a prompt chevron) replaces the orange window
identity across every icon asset: app icon, logo, tray glyph, favicon,
and social preview. Bare macOS binaries (`cargo run`) now show the icon
in the Dock too. (#124)
### Fixed
- **Linked git worktrees group under their main repository** — the sidebar
keys groups on the repository home instead of each worktree's own root,
so a repo and its worktrees share one header while branch status stays
per-worktree. (#118)
- **macOS tray icon stays a template image in every state** — the
attention state no longer swaps in a grey non-template glyph that was
illegible on many menu-bar appearances; agent status lives in the
tooltip and tray menu. (#122)
- **No more console-window flashes from Windows agent hooks** — each hook
emitter frees its throwaway console before it can paint (debug builds
only; release builds were unaffected). (#119)
## [26.7.0] - 2026-07-16
First CalVer release: versions are now `YY.M.PATCH`, so the number says when
it shipped rather than what changed.
### Added
- **Coding-agent detection on Windows** — the agent status dot now works on
Windows: agents are detected from the shell-integration command capture
(no `/proc` there), hook status events reach the daemon via the agent's
ancestor console, and mark-derived detection only re-fires when the
command capture actually changes. (#115)
- **Clipboard image paste for agents off macOS** — pasting a screenshot into
a coding-agent pane on Windows/Linux stages the image to a temp file and
pastes its shell-escaped path (the same route drag-and-drop uses); Windows
BMP screenshots are transcoded to PNG since agent vision rejects BMP.
macOS keeps the higher-fidelity pasteboard read. (#117)
- **Nightly build channel** — `main` is built every night into a rolling
`nightly` prerelease with all six platform artifacts; the in-app update
check is prerelease-aware so nightly users ride the channel while stable
users never see it. (#114, #116)
- **Sidebar groups tabs by git repository** — vertical tabs cluster under a
repo header, groups persist across restarts, ⌘N numbering follows visual
order, and same-named repos are disambiguated by their parent directory.
(#110)
- **System tray icon with agent status menu** — a tray/menu-bar icon
summarizes agent activity across sessions and jumps to a pane from its
menu. (#105, #109)
- **Gradient and image theme backgrounds** — themes can render gradient or
image backgrounds with global window opacity and blur, hot-reloaded like
the rest of the theme. (#106)
- **Double-click a tab to zoom the window** — matching titlebar behavior;
rename moved to the context menu. (#103)
### Changed
- **Settings sheet refinements** — controls right-align in their rows with
hover feedback, the column is tighter, and the theme card is richer.
(#108)
- **Settings copy polish** — clearer wording throughout, and the SSH
security defaults stay visible instead of hiding behind a toggle. (#104)
### Fixed
- **Ctrl+C after a copy sends SIGINT again** — copying with Ctrl+C and
pasting to the PTY now consume the selection, so the next Ctrl+C reaches
the program instead of copying the same text twice. (#113)
- **Shell vi mode is supported** — vi-mode prompts are detected via durable
signals and no longer confuse the prompt gap hold. (#102, thanks @ayamir)
- **App cursor shape is respected** — programs that set the cursor shape
(e.g. vim) see it honored. (#101, thanks @ayamir)
## [0.17.0] - 2026-07-16
### Added
- **Per-tab context menu with worktree tabs** — right-clicking a tab chip or
sidebar row opens a menu: rename, splits, copy working directory, and a
close group. *New Worktree Tab* creates a git worktree under the repo's own
`.tty7/worktrees/<name>` (kept out of `git status` by a self-ignoring
`.gitignore`), with editable name / branch / start point and a live path
preview. Closing a tab that sat in a managed worktree offers to remove the
checkout — unless another pane still lives in it, and dirty checkouts
default to keeping. (#96)
- **Unread pane count in the tab status dot** — a split tab can finish
several agent turns while you're away; the green Done dot now swells into
a badge counting the unread panes (clamped at 9) and shrinks back once
every pane has been seen. (#98)
### Changed
- **The sidebar diff overlay is per-tab and side-by-side** — the overlay now
lives on its tab: switching away hides it, switching back restores it
(re-probing when the status cache disagrees), closing the tab drops it,
and Esc keeps working. The body switches from a unified diff to a
GitHub-PR-style side-by-side view with positionally aligned
removed / added columns. (#100)
- **macOS-style popup panels** — menus get a rounded pill highlight, inset
hairline separators, a 10px panel radius, and a floatier shadow;
searchable lists get a taller Spotlight-style search row, and the palette
viewport holds a whole number of rows so the last visible row is never
cut mid-height. (#97)
- **README rewritten as a minimal index** — feature details, keybindings,
and performance notes moved to `docs/features.md` (en + zh-CN); the
tagline now positions tty7 as a terminal workbench.
### Fixed
- **The agent dot no longer sticks on Waiting after you approve a
permission** — Claude Code has no "permission replied" hook, so a new
PostToolUse hook emits a tool-complete event and the first tool that
finishes after approval flips the dot back to Working. Existing hook
installs surface as Outdated in Settings → Agents with an Update
button. (#99)
## [0.16.1] - 2026-07-15
### Fixed
- **Sidebar diff overlay only opens from the `+N`/`N` counts** — clicking
anywhere on a tab row's git line (branch icon, branch name) used to toggle
the diff overlay, hijacking ordinary clicks on the lower half of the row.
Now only the diff counts are the click target; the rest of the line
activates the tab like the rest of the row. (#95)
## [0.16.0] - 2026-07-15
### Added
- **Click a sidebar git line to open a working-tree diff overlay** — the
branch/diff row in the sidebar is now clickable and opens an in-app overlay
showing the working-tree diff against `HEAD`, file by file with expandable
hunks. It rides the shared git-status signal: when fresh numbers land that
disagree with what it shows, it re-probes the full diff so the overlay stays
live. (#92)
- **Window size and position are remembered across launches** — tty7 saves
the window geometry on quit and restores it next launch, re-centering if the
saved bounds no longer overlap any display. Can be toggled off with the
`remember_window_size` config key. (#94)
### Fixed
- **Attach replay no longer duplicates TUI output into scrollback** — the
daemon's replay ring is now segmented by the geometry its bytes were
recorded under, and attach replays a `Size` → `Snapshot` pair per segment.
Previously the whole ring replayed at the final PTY width, so any resize
during a session (a pane split, a window drag) re-wrapped older output and
a TUI's cursor-up redraws (Claude Code's inline renderer, most visibly)
landed mid-frame, flooding the reattached pane's scrollback with stale
frame copies that never existed live. The ring also caps its segment count
so a long-lived session with many resizes can't grow it without bound. (#91)
- **Agent hooks no longer hang when stdin is a terminal** — the hook runner
skips the stdin read when fd 0 is a tty, so a hook invoked interactively
(rather than with a piped payload) emits the bare event instead of blocking
forever on a read that never returns. (#93)
## [0.15.0] - 2026-07-15
### Added
- **Daemon protocol version handshake** — the GUI asks a running daemon
which wire protocol it speaks before reusing it; after an app upgrade a
mismatched daemon is kept alive and a prompt offers Keep Sessions or
Restart Daemon instead of silently killing every persisted session. (#90)
### Changed
- **Tab close affordance** hides until hover on the active tab too, so the
sidebar and tab strip read clean. (#90)
- **Command palette** no longer offers the Claude-only hook install entry;
Settings → Agents owns hook installs with per-agent state.
### Fixed
- **SSH connection state** shows as a corner status dot on the tab avatar
(amber connecting, green connected, red failed) in the same semantic
colors as agent dots, replacing a theme-grey border ring that read as no
state at all. (#90)
- **Sidebar git branch/diff line** is shared per repository: panes in one
work tree read one snapshot refreshed by whichever pane probed last, so
rows for the same directory no longer show stale or missing +/ counts.
(#90)
- **Command palette** no longer pins Connect/Save rows above command
matches for bare words like `java`; QuickConnect rows require a
host-like query (`@`, `:` or `.`). (#90)
## [0.15.0-beta.1] - 2026-07-15
### Added
- Per-agent hook integrations (Claude Code, Codex, Copilot CLI, OpenCode,
Pi) with install state and actions in Settings → Agents. (#87)
- CLI coding agents and the git branch are recognized and shown in the
sidebar. (#85)
- Multi-line prompt editor, plus an I-beam mouse pointer over text. (#80)
- SSH: Unix GSSAPI (Kerberos) authentication. (#81)
### Changed
- Splitting an SSH pane opens another SSH pane on the same host. (#83)
### Fixed
- The grid shifts up when wrapped command input overflows the bottom of
the screen, keeping the caret visible. (#86)
- Each tab keeps its own active pane across tab switches. (#84)
- The theme panel stays on-screen on narrow windows. (#82)
## [0.14.0] - 2026-07-14
### Added
- SSH connection manager: a native russh client with saved connection
profiles, password and public-key auth, port forwarding, and SFTP. (#74)
- Buffer search overhaul — richer in-terminal search with rebindable,
cross-platform shortcuts. (#75)
- Vertical tab sidebar, and Settings reworked into a full-window page. (#70)
- Tab title now follows the active pane. (#73)
- New Settings controls: bell, notify threshold, mouse reporting, and
session restore. (#68)
### Changed
- SSH saved profiles are now the single source of truth for connections. (#77)
- Bump memchr 2.8.2 → 2.8.3. (#69)
### Fixed
- SSH auth-sheet polish and softer primary buttons. (#79)
- The Cmd+F find bar now owns the top-right slot over the SSH action
icons. (#76, #78)
- Windows: stop the daemon before install/uninstall so it can replace
`tty7.exe`. (#72)
- Moved SSH forwards into the pane context. (#71)
## [0.13.0] - 2026-07-13
### Added
- SSH loopback forwarding for links. (#58)
- Editable keybindings: rebindable shortcuts, pane/tab actions, and a tmux
preset. (#65)
### Fixed
- Trim the first tab's left gap flush to the traffic-light reserve. (#62)
- Remove the active-pane corner indicator dot. (#63)
- CI: format code and platform-gate the ctrl glyph in the keymap test. (#67)
## [0.12.0] - 2026-07-13
### Added
- Ship a Linux AppImage alongside the tarball. (#55)
- Title-bar overflow menu for the command palette and settings. (#57)
### Changed
- Redesigned theme picker with a slide-in panel. (#56)
### Fixed
- Stop the title-bar strip from clipping the Windows close button. (#60)
- Restore the original terminal-window logo, reverting the branding
change. (#59)
## [0.11.0] - 2026-07-12
### Added
- File-based themes, an in-app theme editor, and a UI/branding refresh. (#54)
## [0.10.0] - 2026-07-11
### Added
- Tab completion now executes the completion specs' *dynamic generators*:
positions whose candidates come from the live system get real values — git
branches on `git checkout <Tab>`, container names for docker/podman,
`package.json` scripts for npm/pnpm/bun/yarn, cargo/rustup/tmux/brew/apt/pip
listings, and more. Scripts run off the main thread in the session's cwd
(800 ms timeout, output capped, short-lived cache) and their results merge
into the already-open menu as they arrive; a slow or failing generator just
contributes nothing. (#52)
- When shell integration never engages in a pane, pressing Ctrl+R now explains
why the history menu can't appear (once per pane, dismissed by the next
keystroke) instead of failing silently — naming the wrapper when a
figterm-style PTY shim (`kiro-cli-term`, `figterm`, `qterm`) is intercepting
the shell's OSC 133 reports. The chord still reaches the shell, so its own
reverse-i-search keeps working. (#46)
### Fixed
- `ssh <Tab>` (and scp/sftp/rsync) now completes host aliases from
`~/.ssh/config` — `Include` files honored, wildcard patterns skipped — and
hosts from `known_hosts`, instead of falling back to listing the current
directory. (#51)
## [0.9.0] - 2026-07-10
### Changed
- Ctrl+R history search is now a browsable menu: matching is fuzzy
(subsequence with word-boundary/consecutive bonuses; space-separated terms
must all match) blended with frecency, and the ranked candidates float
beside the prompt — matched characters highlighted, selection moved by
Ctrl+R/↓ and Ctrl+S/↑, Enter to edit, Cmd+Enter to run outright. An empty
query lists the whole history by frecency, so bare Ctrl+R is a "recent &
relevant" browser. The classic `(reverse-i-search)` line stays. (#45)
- History records now carry when the command ran and its exit code: new
entries are `<ts>\t<exit>\t<cwd>\t<command>`, written when the command
*finishes* (zsh `INC_APPEND_HISTORY_TIME`-style, exit code sniffed from
OSC 133;D daemon-side); older formats still load. The Ctrl+R menu shows
"ran 3h ago" and a `` badge on commands whose last run failed; timestamps
from zsh/bash history files are carried over when seeding. (#45)
## [0.8.0] - 2026-07-10
### Added
- Copy on select: an opt-in Settings → Terminal → Clipboard toggle (config
key `copy_on_select`) that copies a mouse selection — drag, double-click
word, or triple-click line, over terminal output or the prompt's command
editor — to the clipboard the moment the gesture ends, no ⌘C needed. Off
by default so a stray selection never overwrites the clipboard. (#34)
### Fixed
- The held-⌘ tab-number badges no longer stick on after the window loses key
status mid-hold (⌘-Tab, Spotlight, a click into another app). The ⌘ release
goes to whatever app is key by then, so the window never saw it; the badges
— and any pending reveal — are now dismissed on the activation flip itself.
## [0.7.0] - 2026-07-10
### Added
- Terminal ANSI colors (`color0``color15`) can now be overridden individually
via `ansi_colors.*` in `config.json`, layered on top of the active theme
preset — with a color picker per slot under Settings → Appearance → ANSI
Colors. Malformed values are ignored, and clearing an override falls back to
the preset's palette. (#37)
- Font ligatures can now be enabled for terminal text. A new optional
`font_features` config passes OpenType features (e.g. `{"calt": true}`)
through to the renderer, and Settings → Appearance grows a toggle for the
common `calt`/`liga` pair. Ligatures stay disabled by default for cell-grid
safety, and changes hot-apply to open panes. (#38)
### Fixed
- Ctrl+L now clears the screen while the prompt-local line editor is active.
The readline dispatcher used to swallow it as an unrecognized chord; it now
forwards the same form-feed byte the raw terminal path sends, so the shell
repaints its prompt as expected. (#36)
## [0.6.2] - 2026-07-08
### Changed
- Context menus and the "+" dropdown now highlight the hovered row with the
same soft fill the command palette uses for its selected row, instead of the
stock saturated accent that snapped hard against the rest of the UI. The
hover text stays at the normal foreground so it reads clearly on the quieter
fill.
### Fixed
- On Windows, a pane no longer hangs open when its shell exits on its own.
Typing `exit`, pressing Ctrl-D, or a shell crash ends the shell, but ConPTY's
output pipe never reports EOF on a natural exit — and tty7 detected a shell's
death solely from that EOF — so the pane was left wedged open, dead but
visible. A Windows-only monitor now waits on the shell process directly and
reports the exit through the same path a Unix `read()` EOF drives, so the pane
closes as it does everywhere else. Closing a tab from the UI was already
unaffected; macOS and Linux are unchanged. (#30)
- Nerd Font prompt icons no longer render sliced off on the right. A non-Mono
Nerd Font (and the proportional ``/`` the OS cascade hands back when nothing
in your font list covers them) gives an icon a single-cell *advance* but draws
ink up to ~1.9 cells wide, and tty7 clipped every lone glyph to exactly one
cell — severing the overflow into the half-icons and cut-off arrow from the
report. A single glyph now paints into a two-cell window, so it renders whole
(bleeding into the trailing blank the way iTerm2 and Terminal.app do), bounded
at two cells so a stray face can't smear across the row. Pairs with the native
powerline separators from #19; Mono Nerd Fonts are unchanged. (#17)
- New tabs and splits no longer stall for seconds while a zsh plugin manager
reinstalls itself. tty7 launches zsh through a throwaway `ZDOTDIR` (so it can
layer its shell integration on top of your config), but it used to leave
`ZDOTDIR` pointing at that empty temp dir the whole time — so tools that find
their own state via `${ZDOTDIR:-$HOME}` (Zim, oh-my-zsh, `compinit`'s
`.zcompdump`) looked in the wrong place and rebuilt from scratch on every
pane, e.g. Zim reprinting `modules/…: Installed` and hanging for ~3s. Each
redirector now points `ZDOTDIR` back at your real config dir while your
startup files run, and restores it for the live session, so plugin managers
and completion caches resolve correctly and load instantly. As a bonus this
also fixes the classic relocated-config layout (a tiny `~/.zshenv` that sets
`ZDOTDIR=~/.config/zsh`), which previously loaded your config but silently
dropped tty7's integration. (#15)
## [0.6.1] - 2026-07-08
### Fixed
- Tab completion (and other line editing) now stays out of the way over `ssh`.
A remote shell that emits its own prompt marks — fish 4.x on a Linux server,
most visibly, which ships OSC 133 on by default — used to engage tty7's
*local* line editor, so Tab ran completion against the local machine's
filesystem instead of reaching the remote shell. tty7 now only drives the
inline editor while the shell it launched is itself idle at its prompt;
whenever a foreground command (ssh, a TUI, a nested shell) owns the terminal,
keystrokes pass straight through to it. (#26, follow-up to #18)
## [0.6.0] - 2026-07-08
### Added
- The "+" button now opens a shell picker: tty7 detects the shells installed
on this machine (on Unix the login shell, `/etc/shells`, plus well-known
shells found on `PATH` — fish, nushell, pwsh and friends installed by
Homebrew/nix are never registered in `/etc/shells`; on Windows PowerShell 7,
Windows PowerShell, Command Prompt, Git Bash and WSL distributions)
and lists them in a dropdown, so opening a tab in a different shell
no longer requires editing `config.json`. The default entry leads the menu,
⌘T / Ctrl+T still opens a default tab in one keystroke, and splitting a pane
inherits its shell — a fish tab splits into more fish, not back to the
default. Shells picked this way aren't remembered across restarts (restored
panes re-attach to their still-running shells anyway).
### Changed
- The Windows default shell now prefers PowerShell 7 (`pwsh.exe`) when
installed — probed across Program Files (x64/x86/ARM), the Microsoft Store,
scoop, dotnet tools and `PATH` — and falls back to Windows PowerShell as
before. Set `shell` in `config.json` to override, as ever.
### Fixed
- Powerline prompt separators (powerlevel10k, oh-my-posh, oh-my-zsh) now render
pixel-perfect at any font, size and line-height: the eight solid separators
(sharp triangles, round caps, slants) are drawn natively as fill paths sized
to the exact cell instead of relying on a Nerd Font, so segments meet their
backgrounds cleanly with no gaps, narrow wedges or tofu. The bundled Hack font
is also appended to every font-fallback chain, so common prompt glyphs (➜, ,
box drawing) no longer render truncated or missing when no Nerd Font is
installed. (#17)
- A URL glued directly to a full-width bracket with no space — e.g.
`…/pull/343fix/… → dev` — no longer swallows the bracket text into the
link. URL detection now stops at the first non-URL character (a CJK glyph,
full-width bracket, arrow or emoji), while interior ASCII parens
(Wikipedia-style URLs) are still preserved.
- Fish, nushell, pwsh and other shells installed by Homebrew or nix now appear
in the "+" shell picker even when they aren't registered in `/etc/shells`
(which those package managers leave to the user): a curated set of well-known
shells is now probed on `PATH` as a catch-all, after the `/etc/shells`
entries. (#18)
- Upgrading tty7 while an older daemon is still running in the background no
longer breaks new tabs. A stale daemon that accepts the connection but can't
serve the new client's request is now restarted once and retried
automatically; on macOS the GUI also forwards the shell it was launched with
to the detached daemon, so panes use the right shell instead of a stale
`$SHELL` inherited from LaunchServices.
## [0.5.0] - 2026-07-07
### Added
- Windows releases now ship an Inno Setup installer
(`tty7-<version>-windows-x86_64-setup.exe`) alongside the portable zip. It
installs per-user by default (no admin prompt, with an all-users option),
adds a Start Menu shortcut and an "Apps" uninstall entry, and offers an
optional desktop icon. Still unsigned, so SmartScreen warns on first launch.
- Startup update check: tty7 asks GitHub once, in the background, whether a
newer release has shipped. If so, it pops a one-time "Update available" dialog
(once per version — remembered in `update.json`, so it never nags twice for
the same release) and keeps a persistent "Download" prompt in Settings →
About. Both open the Releases page; tty7 never downloads or updates itself —
you still install by hand. Turn the check off with `check_for_updates` in
`config.json` or the "Check for updates on launch" toggle in About. A failed
or offline check is silent.
- ⌘K (Ctrl+K on Windows/Linux) clears the screen and scrollback — the same
"Clear" the right-click menu already offered, now on the keyboard shortcut
Terminal.app, iTerm2, and Ghostty users expect. Also available from the
command palette, and remappable as `ClearScrollback` in `keybindings`.
- ⌘⏎ toggles window fullscreen (new `ToggleFullscreen` action, also in the
View menu and command palette), matching the Ghostty/iTerm2 default. It
previously toggled pane maximize — which silently did nothing in a
single-pane tab, so the chord felt dead.
- The right-click menu now shows each item's keyboard shortcut. Copy, Paste,
Select All, and Find previously showed nothing (they're dispatched inline,
with no bound key for the menu to read a hint from) while the other items
did, so the menu looked half-labelled. ⌘A / ⌘F stay hint-less on
Windows/Linux, where those chords keep their readline meaning.
### Changed
- Maximize / restore pane moved from ⌘⏎ to ⌘⇧⏎ (Ghostty's `toggle_split_zoom`
default), making room for fullscreen on the bare chord. An existing
`ToggleMaximizePane` override in `keybindings` still wins.
### Fixed
- Windows: launching tty7 no longer opens a stray console window behind the
app. Release builds are now linked with the `windows` subsystem; debug
builds keep the console so `println!` output stays visible. (#10)
- The right-click "Select All" now matches the ⌘A shortcut: at the prompt it
selects the edited command line, otherwise the whole terminal buffer. It
previously always selected the whole buffer, so click and keystroke behaved
differently at the prompt.
- Ctrl+R reverse-search now accepts plain ASCII keystrokes. The query only
took text from the IME commit path, so a non-CJK input source on macOS — and
all typing on Linux — was swallowed: the search box opened but ate every key.
Reported on V2EX.
## [0.3.0] - 2026-07-07
### Added
- PowerShell shell integration: `powershell.exe` and `pwsh` now emit the OSC 133
semantic-prompt marks and OSC 7 cwd that zsh/bash/fish already do, injected
via `-EncodedCommand` after the user's profile loads (their config is never
touched). This turns on the inline line editor at the PowerShell prompt — so
clicking positions the caret and new tabs/splits inherit the working
directory — which is what previously made mouse clicks a no-op at the prompt
on Windows.
### Fixed
- Typing `exit` (or Ctrl-D) left a dead "process exited" pane behind instead
of closing it. A pane whose shell genuinely ends now closes itself —
collapsing its split, or closing the tab when it was the only pane (the
last tab falls back to the home page), like every other terminal. A pane
that merely *lost its daemon connection* still stays visible: auto-closing
those would silently discard — and kill — sessions that may still be alive
daemon-side. Panes that died while detached clean themselves up on the next
attach the same way.
- A full-screen TUI dying without restoring the terminal — the canonical case
being an ssh session dropping mid-`htop`/`vim` — left the pane stranded on
the alt screen with a hidden cursor and live mouse reporting: a visible
prompt with no cursor anywhere, mouse clicks echoing `0;19;42M`-style junk,
and broken scrollback. The client now scrubs this residue the moment the
shell reports its next prompt (OSC 133): it leaves the stranded alt screen,
re-shows the DECTCEM-hidden cursor, and disables stale mouse/focus reporting
and kitty keyboard flags — each reset only when its mode is actually set.
Reattach self-heals the same way, since the daemon replays the prompt state
after the ring.
- Windows shell integration never engaged even for the default shell: detection
keyed off `portable-pty`'s `get_shell()`, which reports `%ComSpec%` (cmd.exe)
regardless of what's actually spawned, so the PowerShell default was mistaken
for an unsupported shell. It now resolves to `powershell.exe` directly.
## [0.2.0] - 2026-07-04
### Added
- Underline styles: undercurl, double, dotted, and dashed underlines render distinctly.
- `config.json` hot reload — edits apply to the running app without a restart.
- Desktop notifications driven by OSC 9 / OSC 777 escape sequences.
- Kitty keyboard protocol (CSI u progressive enhancement) for TUI apps like Neovim and Helix.
- Shell integration for bash and fish, alongside the existing zsh support.
- Windows support: cross-platform daemon, PowerShell as the default shell, embedded app icon.
- Linux support: builds against gpui's x11/wayland backends, `/proc`-based foreground cwd + pane-title tracking, Linux CI job, and documented build dependencies.
- Downloadable builds for every platform: the release workflow now packages and uploads all four targets — signed/notarized macOS DMGs (arm64 + x86_64) plus unsigned archives for Windows (`.zip`) and Linux (`.tar.gz`), each via its own `.github/scripts/bundle-<os>` script.
- Settings UI: terminal / appearance / behavior options are configurable from the GUI, with a searchable font-family dropdown and a wider theme gallery.
- Configurable default shell.
### Changed
- Project renamed to **tty7**.
- macOS releases ship as drag-to-Applications DMGs instead of zips, and the
Intel build moved to the `macos-15-intel` runner (`macos-13` was retired,
which had silently kept x86_64 assets from ever publishing).
- Pixel-smooth scrollback: scrolling carries a sub-line fraction and shifts the paint instead of jumping whole lines.
- Smoother scrolling on dense screens: glyph shaping is batched and wakeups are coalesced.
- CJK-dense screens paint ~2.4× faster: consecutive wide glyphs batch into single shaped runs (two columns per glyph) instead of painting cell-by-cell; the grid snapshot buffer is reused across frames and the selection/search overlay scans are skipped when nothing is highlighted. Release builds now use thin LTO.
- Type-ahead is integrated into the line editor instead of being stranded on zle's line.
- New tabs open next to the active tab instead of at the end.
- Terminal throughput ~12× faster (11 MB `cat`: ~2.0 s → ~0.16 s; DOOM-fire: ~47 fps → ~920 fps, both at 155×40 on an M1 Pro — now ahead of Alacritty/Ghostty on the same machine): the daemon's replay ring is a `VecDeque` so a full ring no longer memmoves 8 MiB per ~1 KiB PTY read, and the per-connection writer coalesces queued `Output` frames (≤256 KiB) so a flood reaches the client as a few large frames instead of thousands of tiny ones. A backpressure gate (4 MiB high-water) pauses the PTY reader while the client catches up, so a runaway `yes` can't grow daemon memory without bound. `TTY7_TRACE=1` prints per-second reader-loop accounting on both sides for future diagnosis.
- Second throughput pass, another ~1.4× on bulk output (11 MB `cat`: ~160 ms → ~100 ms; sustained plaintext drain 124 → 148 MB/s, vs ~170 MB/s for a raw do-nothing PTY reader on the same machine; DOOM-fire is unchanged — it is producer-bound at ~96 MB/s): the backpressure high-water grows to 16 MiB so a big burst drains at PTY speed while the client parses in its own time; daemon⇄GUI socket buffers grow from macOS's 8 KiB default to 256 KiB; the client applies consecutive `Output` frames as one batched parser pass (one term-lock + wakeup per burst, latency-free — the batch never waits for unarrived bytes); the shared OSC tokenizer skips Ground/Ignore runs with SIMD `memchr`; the gate's hot path is a lock-free atomic (previously a Mutex plus an unconditional `notify_all` per socket write); and the four threads on the interactive output path ask macOS for `USER_INTERACTIVE` QoS to stay off the efficiency cores (`TTY7_NO_QOS=1` opts out).
### Fixed
- A long `--config-dir` path crashed the GUI at startup ("path must be shorter than SUN_LEN"): when `<config>/daemon.sock` would exceed the OS socket-path limit (104 bytes on macOS), the endpoint now falls back to a short per-user path keyed by a stable hash of the config dir ($XDG_RUNTIME_DIR, else the OS temp dir). Short paths keep the original layout, so existing daemons stay reachable.
- Typing right after a command finished could leave a stray echoed character plus zsh's reverse-video `%` in the scrollback: the "command finished" mark (OSC 133;D) is now emitted the instant the command exits — prepended ahead of the user's precmd hooks (zsh/bash) — instead of after slow prompt frameworks (oh-my-zsh git status, conda), so the local input editor takes keystrokes back hundreds of milliseconds sooner.
- Typing while a command was still running stranded those keystrokes on zle's line at the next prompt — un-editable and double-drawn under the line editor's overlay. Type-ahead adoption (wipe the shell's line, seed the editor) now runs at every prompt, not just the shell's first, and the wipe waits until zle is actually reading (the live `133;B` mark) so it is consumed silently instead of being kernel-echoed into the scrollback as a literal `^U`.
- Typing ahead of a fast command left kernel-echoed debris in the scrollback (`ls` plus zsh's reverse-video `%`). Reconstructable gap input is now held client-side for up to 150 ms: a command that finishes inside the window hands the keystrokes straight to the line editor with the PTY untouched — zero echo; a longer command (or one reading stdin) gets the bytes released verbatim, so REPLs and password prompts still work.
- fish shell integration silently never installed, so fish users got no prompt marks or cwd tracking.
- **Security:** pasted clipboard content is stripped of ESC bytes, closing a bracketed-paste escape that could inject auto-executing commands.
- Crash when copying/cutting right after a forward word/line delete left a stale selection anchor.
- `Ctrl+Alt+<letter>` was indistinguishable from `Ctrl+<letter>` because the legacy key encoder dropped the Alt ESC prefix.
- Plain Enter/Tab/Backspace were wrongly CSI-u-encoded at the kitty-keyboard DISAMBIGUATE level, which could wedge the shell after a crashed TUI.
- No-op edits (e.g. Backspace at the start of the line) no longer swallow the first undo.
- OSC scanners (daemon-side and notification-side) dropped a well-formed sequence that directly followed an unterminated one.
- Daemon pane teardown is hardened: process-group kill, bounded join, dead panes are reclaimed.
- New shells default to `$HOME` when launched from the app bundle with cwd `/`.
## [0.1.0] - 2026-06-30
Initial release.
- Sessions live in a persistent daemon and survive window close / app restart.
- GPU-rendered terminal grid on [gpui], backed by Zed's `alacritty_terminal` fork.
- Tabs and pane splits (split right/down, maximize, focus movement).
- Command palette with fuzzy search over every action.
- Smart line editing: inline completion, syntax highlighting, history, in-terminal search.
- 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
[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
[0.7.0]: https://github.com/l0ng-ai/tty7/compare/v0.6.2...v0.7.0
[0.6.2]: https://github.com/l0ng-ai/tty7/compare/v0.6.1...v0.6.2
[0.6.1]: https://github.com/l0ng-ai/tty7/compare/v0.6.0...v0.6.1
[0.6.0]: https://github.com/l0ng-ai/tty7/compare/v0.5.0...v0.6.0
[0.5.0]: https://github.com/l0ng-ai/tty7/compare/v0.4.0...v0.5.0
[0.4.0]: https://github.com/l0ng-ai/tty7/compare/v0.3.0...v0.4.0
[0.3.0]: https://github.com/l0ng-ai/tty7/compare/v0.2.0...v0.3.0
[0.2.0]: https://github.com/l0ng-ai/tty7/compare/v0.1.0...v0.2.0
[0.1.0]: https://github.com/l0ng-ai/tty7/releases/tag/v0.1.0
[gpui]: https://github.com/zed-industries/zed