The "+" button now opens a shell picker listing every shell found on
this machine — the login shell plus /etc/shells on Unix; PowerShell 7,
Windows PowerShell, cmd, Git Bash and WSL distributions on Windows — so
opening a tab in a different shell no longer requires hand-editing
config.json. The default entry leads the menu (Cmd+T still opens a
default tab in one keystroke), and splits inherit the pane's shell.
The Windows default shell now prefers PowerShell 7 when installed,
probed the way Warp does it (Program Files x64/x86/ARM, the Microsoft
Store shim, scoop, dotnet tools, then PATH), falling back to the
Windows PowerShell that ships with the OS.
Spawn precedence in the daemon is now: explicit per-spawn pick >
config.json `shell` > platform default. Wire compat is preserved across
GUI/daemon version skew: a default spawn keeps the legacy SPAWN frame
byte-for-byte, so an old daemon still serves a new GUI; only an
explicit pick uses the new SPAWN_SHELL kind, locked by a round-trip +
legacy-frame test.
Claude-Session: https://claude.ai/code/session_01ABey161AUxhgmJC3PRoYtF
Add a Discord badge to the README badge row and a chat invite in the
Contributing section (both English and Simplified Chinese). Remove
SECURITY.md and its references; private vulnerability reporting stays
available through GitHub's repository setting.
Claude-Session: https://claude.ai/code/session_01ABey161AUxhgmJC3PRoYtF
Promote the [Unreleased] changelog to [0.5.0] (Windows installer, startup
update check, ⌘K clear, ⌘⏎ fullscreen, right-click menu shortcut hints) and
add this release's fixes (right-click Select All matching ⌘A, Ctrl+R ASCII
keystrokes). Bump the crate version 0.3.0 → 0.5.0 so the built binary reports
its real version to the update check, and backfill the missing [0.3.0]/[0.4.0]
changelog compare links.
Copy / Paste / Select All / Find are dispatched inline in
`handle_cmd_shortcut` with no registered `KeyBinding`, so gpui-component's
PopupMenu couldn't auto-derive their hints the way it does for the
keybinding-backed items (Clear, Split, Maximize, New Tab, Close Pane) — the
menu showed shortcuts on some rows and nothing on others. Render the hints
ourselves via `menu_row_with_hint`, styled to match the auto-rendered ones,
instead of registering real bindings (which would risk the Ctrl+C SIGINT
fall-through on Windows/Linux). ⌘A / ⌘F are macOS-only, so those hints are
gated behind `mac_only`.
Also fix the "Select All" menu item diverging from ⌘A: both now go through
`select_all_contextual` (select the edited command line at the prompt,
otherwise the whole terminal buffer) so click and shortcut stay in sync.
The reverse-search query only took text from the IME commit path
(input_text -> push_query), so a CJK input source could type into it but a
plain ASCII source — and Linux, where prefers_ime_for_printable_keys is
false — delivered printable keys via on_key_down, where handle_key ignored
them. Ctrl+R opened, but the search box swallowed every keystroke.
Forward printable key_char events from handle_reverse_search_key into
push_query, mirroring the editor's existing key_char path; control/Cmd/Alt
chords and non-printable keys still fall through to the control-key handling.
Fixes ASCII typing on macOS and all typing on Linux. Reported on V2EX.
Claude-Session: https://claude.ai/code/session_01ABey161AUxhgmJC3PRoYtF
Two CodeRabbit findings on core::update:
- parse_version now rejects malformed cores instead of truncating them:
a doubled prefix (`vv0.3.1`) and extra components (`0.3.1.1`) no longer
collapse to a valid triple, so an unrecognizable tag can't surface a
bogus "update available". Uses strip_prefix('v') (not trim_start_matches)
and rejects a 4th dot component. Added tests for both cases.
- The check now races an explicit 15s timeout (ReqwestClient only sets a
connect timeout), so a connected-but-stalled response fails soft
deterministically instead of sitting pending for the session.
bundle-windows.ps1 now compiles windows-installer.iss (ISCC is preinstalled
on windows-latest) from the same staged payload as the zip, producing
tty7-<version>-windows-x86_64-setup.exe: per-user install by default with an
all-users option, Start Menu shortcut, Apps uninstall entry, optional desktop
icon. Release workflow uploads the new artifact; READMEs and CHANGELOG updated.
Set `windows_subsystem = "windows"` on release builds so launching the
packaged .exe doesn't flash a console window, while keeping it in debug
builds so println!/eprintln! stay visible during development.
Build on the notify-only check: when a newer release is found, pop a
native modal dialog ("Update available — Download / Later") in addition
to the passive Settings -> About prompt. Download opens the Releases
page; tty7 still never self-updates.
- Pops exactly once per version: the shown version is recorded in a small
update.json (mirroring session.json's load/save) and re-checked on the
next launch, so we never nag twice for the same release.
- wait_for_window handles the startup race where the network check
finishes before the window opens — poll briefly, then host the modal.
- open_releases_page moves to core::update as a shared free fn; the
Settings -> About button now delegates to it (one opener, two callers).
- Fails soft as before: no window / disabled check / offline all skip the
modal silently; the About prompt still appears whenever we're outdated.
Ask the GitHub releases API once, in the background, on GUI startup
whether a newer version has shipped and — if so — surface a "Download"
prompt in Settings → About that links to the Releases page. tty7 never
downloads or self-updates; the user installs by hand, exactly as the
README's Install section describes.
- core::update: UpdateStatus global, version parse/compare, and the
GitHub fetch via reqwest_client. That reqwest+rustls stack is already
compiled into the tree through gpui-component-assets, so this pins no
new native code — it only exposes what we already build.
- config: check_for_updates toggle (default true) to skip the network
call entirely for offline / privacy.
- Settings -> About: an "Updates" section with the version prompt, a
Download button (default style, matching the sibling actions), and the
toggle.
- Fails soft: no network, a rate-limit, a private/renamed repo, or an
unparseable tag all collapse to "no prompt", logged at debug and never
surfaced. The terminal opens the same whether or not GitHub is reachable.
Typing `exit` (or Ctrl-D) left a dead "process exited" pane behind: the
view marked itself exited, but nothing ever closed the pane or tab.
The reader thread now records whether the pane ended with a *genuine*
child exit (`DaemonMsg::Exited`) as opposed to a daemon disconnect, and
the view surfaces that as a `ChildExited` gpui event. The app subscribes
at the single place all panes are built and closes the emitting leaf:
collapsing its split, or closing the tab when it was the only pane (the
last tab falls back to the home page). The daemon pane is killed so the
session drops it; panes that died while detached clean themselves up on
the next attach, when the daemon replays their exit.
A disconnect deliberately keeps the dead pane visible -- auto-closing
would silently discard (and kill) sessions that may still be alive
daemon-side.
An ssh session dropping mid-TUI (htop, vim, ...) leaves the pane wedged:
the remote app hid the cursor (DECTCEM), entered the alt screen and
enabled mouse reporting, and the restore sequences can never arrive. The
host shell's next prompt then renders with no cursor anywhere, mouse
clicks echo `0;19;42M`-style junk, and scrollback stays broken.
The client's reader thread now scrubs this residue when the daemon
reports a fresh prompt (OSC 133): at that position in the byte stream no
full-screen program owns the pane, so a stranded alt screen, hidden
cursor, mouse/focus reporting, or kitty keyboard flags are provably
stale. Each reset is emitted only when its mode is actually set
(`?1049l` performs a cursor restore when idle, so no blanket resets),
and bracketed paste / application cursor keys are left to the line
editor, which re-arms them on every read. Reattach self-heals the same
way -- the daemon replays the prompt state after the ring.
tty7 is split into two Rust processes: a persistent daemon that owns the
shells and a GPU-rendered client that talks to it over a local socket.
Because the shells live in the daemon, quitting and reopening the app
leaves the session intact — detach and reattach, no tmux required.
- Persistent sessions — the daemon holds the PTYs and child processes, so
closing a window or swapping in a new build never takes a shell down.
- Performance — an 11 MB `cat` completes in 95 ms and DOOM-fire renders at
888 fps; the daemon drains the PTY at device speed off the render path.
- Shell-aware — new tabs and splits open in the current working directory;
zsh, bash, fish, and PowerShell are set up automatically.
- Enhanced prompt — inline completion, syntax highlighting, history, and
in-terminal search, with rich flag/subcommand signatures for common tools.
- Tabs, resizable splits, a command palette, click-to-open links, desktop
notifications, eight themes, and CJK/IME input.
Native builds for macOS, Windows, and Linux.
Built on Zed's gpui and Alacritty's VT core.