mirror of
https://github.com/l0ng-ai/tty7.git
synced 2026-09-22 08:02:24 +00:00
* 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>
1440 lines
84 KiB
Markdown
1440 lines
84 KiB
Markdown
# 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+2500–U+257F and U+2580–U+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.20–1.47:1 to 1.70–1.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/343(fix/… → 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
|