cargo fmt over app.rs/pane.rs/settings.rs/keymap.rs (rustfmt CI job).
key_chords_splits_a_sequence_into_keycap_groups hard-coded ⌃ for the
ctrl modifier, which only renders that way on macOS; elsewhere key_tokens
maps ctrl to "Ctrl", so the test failed on the Linux and Windows runners.
Gate the expected glyph behind target_os like the existing SECONDARY and
SHIFT constants.
Co-authored-by: l0ng-ai <24760907+l0ng-ai@users.noreply.github.com>
Detect foreground SSH sessions in the daemon and cache the active remote context per pane; open Cmd-clicked loopback URLs through daemon-owned ssh -N -L local forwards over a ControlMaster socket; add settings controls to enable SSH loopback forwarding and view/close active forwards. Kept behind an explicit ssh_loopback_forward setting; the daemon validates the pane's foreground process is a plain SSH session and rejects unsafe invocations. Includes a follow-up hardening commit rejecting option-like ssh targets (leading '-') to close a local argument-injection gap.
Drop the tab strip's leading pl_2 to pl_0 so the first chip starts flush against the title bar's left reserve (80px traffic-light on macOS, 12px elsewhere) instead of adding an extra 8px gutter. Right side and per-chip text inset unchanged.
The focused pane is already distinguished by fading inactive panes (opacity 0.55) when a tab is split, so the corner dot was redundant. The focused flag still drives the fade.
Implements issue #61's tmux-like input model in three layers — editable shortcuts (Settings → Keybindings), directional pane focus/resize/swap + relative tab nav + Activate Tab 1-9, and a tmux prefix preset — without parsing ~/.tmux.conf and without changing zero-config defaults. Closes#61.
On Windows the tab strip reserved only 100px on the right for the native
window controls, but gpui-component's TitleBar lays out 12px of left
padding plus three 34px caption tiles (─ ▢ ✕ = 102px) beside the strip —
114px in all. The 14px shortfall let the strip overrun the bar and shove
the ✕ off the rounded corner once the overflow "⋯" was pinned to the
strip's right edge. Reserve the full 114px so the strip's right edge meets
the controls and the "⋯" keeps its inset.
Co-authored-by: thomas <thomas@gmail.com>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* feat(release): ship a Linux AppImage alongside the tarball
The Linux release was a bare, dynamically-linked binary built on
ubuntu-latest, so it only reliably ran on Ubuntu — Fedora/Arch users hit
missing/mismatched runtime libs. Add an AppImage that bundles the
x11/wayland/xkb/fontconfig/freetype libs so it launches across distros.
- bundle-appimage.sh: linuxdeploy populates an AppDir + deps, completions
go beside the binary (usr/bin/completions, matching signature.rs's
current_exe lookup), appimagetool packs it. Runs FUSE-less on CI.
- release.yml: new "Package Linux AppImage" step after the tarball,
libfuse2/file added to the Linux deps, *.AppImage added to the upload
list. The AppImage step avoids `rm -rf dist` so the tarball survives.
- README (en + zh): recommend the AppImage, keep the tarball as the bare
fallback.
Note: glibc is not bundled, so ubuntu-latest still sets the glibc floor.
* fix(release): downscale AppImage icon to a resolution linuxdeploy accepts
linuxdeploy rejected the 1024x1024 app-icon.png (its valid list tops out at
512). Resize to 256x256 with ImageMagick's convert (added to the Linux apt
deps) before handing the icon to linuxdeploy.
---------
Co-authored-by: l0ng-ai <24760907+l0ng-ai@users.noreply.github.com>
* feat(settings): redesign theme picker with a slide-in panel
Redesign the Appearance page's theme UI:
- Replace the inline theme gallery with a compact "Current theme" card
(preview + name + light/dark) that opens a searchable theme picker in
a right-hand panel; applying a theme keeps the panel open so several
looks can be tried in a row.
- Add `theme_panel_open` / `theme_search` to `SettingsState`, plus
toggle/close helpers and an `active_settings_mut` accessor.
- Share one `theme_preview` between the card and the panel cards; keep
the existing bar-based preview shape.
- Group the custom-theme controls (duplicate / color editor / open
folder) under the Theme section instead of stranding them at the foot
of the page, and drop the near-black `.primary()` fill on the
Duplicate button so it fits the soft, mostly-outline sheet.
* style: rustfmt theme panel border_color
---------
Co-authored-by: l0ng-ai <24760907+l0ng-ai@users.noreply.github.com>
* feat(ui): add a title-bar overflow menu for palette and settings
The command palette and settings panel were reachable only by keyboard
(⌘P / ⌘,) — no on-screen affordance existed, so the app leaned entirely
on shortcuts for its two most useful non-terminal entry points.
Add a ghost "⋯" button on the title bar's otherwise-empty right edge
(same 30px tile rhythm as the "+"), opening a small dropdown:
- Command Palette ⌘P
- Settings… ⌘,
Both rows dispatch the real gpui action, so a click and the shortcut
share one path and each row auto-renders its keybinding hint. The menu
is hidden while the settings tab is active (both entries are redundant
there). On Windows/Linux the window controls sit on the right, so the
button gets extra right padding to read as a menu, not a fourth control.
* fix(ui): keep the title-bar overflow menu from drifting into the corner
The '⋯' is pinned to the right edge of a strip that used '.w_full()', but
the title bar sizes its content by intrinsic width, so 'w_full' never
tracked the window. Shrinking the window left the strip's right edge —
and the pinned '⋯' — lagging behind, sliding the button into the rounded
corner where it clipped and read cramped.
Derive the strip width from the live viewport instead, mirroring the cap
already used for the chip row, so the right edge tracks the window at
every size and the '⋯' holds its original tight 8px inset on resize.
---------
Co-authored-by: l0ng-ai <24760907+l0ng-ai@users.noreply.github.com>
The #54 branding refresh replaced the original line-art logo with a
heavier Slate-tile + gradient-ring mark. Restore the original assets
(rounded terminal window frame, title-bar divider, orange cursor block)
across svg/png/icns/ico, reverting to the pre-#54 versions.
Co-authored-by: l0ng-ai <24760907+l0ng-ai@users.noreply.github.com>
Replace compiled-in presets + colors.*/ansi_colors.* overrides with a file-based theme system: a serializable seed (bg/fg/accent/cursor/selection + ANSI-16, optional gradient/image/opacity/blur) with all chrome derived, light/dark inferred from luminance behind a WCAG guard, built-ins + user YAML + on-the-fly iTerm2 import via a hot-reloaded registry, and an in-app duplicate-to-edit color editor. Also: prompt-editor shift-click/word-drag selection and cross-platform word keys, ghostty-style tab labels, flat menu highlights, a redesigned app icon, the Background Service -> Daemon rename, and a gated TTY7_PROFILE build-timing probe.
* feat(completion): execute dynamic generators for live candidates
The completion engine consumed Fig specs' static shape but never ran
their dynamic generators, so positions whose candidates come from the
live system — ssh hosts, git branches — fell through to filesystem
path completion (#51: ssh <Tab> listed the cwd).
Local-only by design: the pure engine returns each pending script, the
view runs it on the background executor (/bin/sh -c in the session
cwd, 800ms timeout, kill-on-drop, 256KiB stdout cap, 5s TTL cache) and
merges the parsed lines into the open menu, generation-tagged so a
result can't outlive its session. A per-script parser registry ports
the specs' dropped postProcess transforms (git markers, docker
{{json .}}, package.json scripts, …); unmatched scripts default to
one-candidate-per-line, and hopeless outputs are suppressed rather
than inserted as garbage.
ssh/scp/sftp/rsync specs gain host generators reading ~/.ssh/config
(Include-aware, wildcard patterns skipped) and known_hosts (hashed
entries skipped, [host]:port unwrapped).
Fixes#51
Co-authored-by: l0ng-ai <24760907+l0ng-ai@users.noreply.github.com>
* docs(comments): describe borrowed conventions directly
Prior-art name-drops in comments aged poorly as the implementations
diverged; keep the behavioral rationale, drop the product citations.
* fix(completion): make generator execution CI-portable
Linux: sh -c may fork the command instead of exec'ing it, so killing
only the shell on timeout left a grandchild holding the stdout pipe —
the reader (and the caller) then blocked until the grandchild exited
on its own. Spawn the child as its own process-group leader and kill
the group; the timeout test now forces the fork case (trailing true)
so the group-kill is what's actually proven.
Windows: generator scripts are POSIX sh + awk, so the execution path
now compiles to no-suggestions there instead of failing at runtime on
a missing /bin/sh; the process-spawning tests are Unix-only to match.
---------
Co-authored-by: l0ng-ai <24760907+l0ng-ai@users.noreply.github.com>
Follow-up to #49: iTerm2-style semantic paths — an existing directory in
the row text links like a file does, and the system opener (open /
xdg-open / explorer) already handles directories natively, so detection
is the only change. Bare paths only: a token carrying a :line suffix
still requires a file, so localhost:8080 can't link just because a
directory named localhost exists in the cwd.
Co-authored-by: l0ng-ai <24760907+l0ng-ai@users.noreply.github.com>
* feat(links): add file path links
* fix(links): reset the cached link modifier on activation flips, cover all tabs
The per-pane link_modifier_down cache was refreshed only for the active
tab's leaves, and never on window (de)activation — so releasing Cmd after
a mouse tab-switch, or during Cmd-Tab/Spotlight (the release lands in
whatever app is key by then), left panes stuck at true. A stale true makes
a plain unmodified left click open links and steals clicks from
mouse-tracking TUIs.
Route the refresh through a helper that walks every tab, and treat the
window-activation flip as a release, exactly like the badge dismissal
right next to it. Also reword the search.rs docs that still described the
now test-only url_at as the production entry point.
---------
Co-authored-by: l0ng-ai <24760907+l0ng-ai@users.noreply.github.com>
* feat(view): explain a dead Ctrl+R instead of failing silently (#46)
When shell integration never engages in a pane — typically because a
figterm-style PTY shim (kiro-cli-term, qterm) exec'd over the shell and
swallowed its OSC 133 reports — the whole command-editor overlay is
absent by design, and Ctrl+R used to fall through to the raw PTY with no
hint of why the history menu didn't appear.
Now that raw-path Ctrl+R raises a one-shot, per-pane notice (floating
bottom-right) saying integration hasn't engaged, refined off-thread with
the daemon's foreground-process name when it matches a known shim: the
wrapper is the culprit worth naming, since "install integration" advice
would mislead — the hooks are installed, something between the shell and
tty7 is eating their output. The chord still reaches the PTY, so the
shell's own reverse-i-search keeps working as the fallback.
Guards keep it honest: silent inside an 8s startup grace window (slow rc
files legitimately haven't reported yet), on the alt screen, or once
integration has engaged (a running foreground command is then the
obvious reason); retracted if a slow shell engages late; dismissed by
the next keystroke or a 15s timeout.
* docs(readme): restructure around Why/Features, cut prose and boilerplate
Replace the prose About section with a four-point Why tty7 list, split
Features into prompt vs window groups written as one-line benefit
bullets, drop emoji section headers, fold acknowledgements/contributing/
license into a one-line footer, and keep zh-CN in sync throughout.
* feat(history): Ctrl+R fuzzy search menu with run metadata
Ctrl+R grows from the single-line reverse-i-search into a browsable
menu of ranked candidates floating beside the prompt:
- Matching is fuzzy (src/terminal/fuzzy.rs, a dependency-free
affine-gap aligner: word-boundary and consecutive-run bonuses, gap
penalties; space-separated query terms must all match), blended with
the existing frecency scores so a command you run constantly — or
ran in this directory — outranks an equally-good textual match.
- An empty query lists the whole history by frecency, so bare Ctrl+R
is a "recent & relevant" browser. Matched characters highlight in
the rows; Ctrl+R/Down and Ctrl+S/Up move the selection, Enter loads
the line into the editor, Cmd+Enter runs it outright. The classic
(reverse-i-search) prompt line stays.
- History records now carry run metadata: new lines are
<ts>\t<exit>\t<cwd>\t<command>, written when the command finishes
(zsh INC_APPEND_HISTORY_TIME-style) so the exit code sniffed from
OSC 133;D lands in the record; older formats still parse, and
zsh/bash HISTFILE timestamps carry over when seeding. The menu shows
"ran 3h ago" and a red x-badge on commands whose last run failed.
- RemoteTerminal exposes prompt_seq/last_exit_code so the view can
tell a fresh post-command prompt report from the stale pre-submit
state even when 1 Hz polling misses a fast command's running window.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* style: cargo fmt
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
---------
Co-authored-by: l0ng-ai <24760907+l0ng-ai@users.noreply.github.com>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
ASCII () and [] are legal URL characters, so the URL token scan let an
orphan closer glue the surrounding prose onto the link:
'(https://github.com/l0ng-ai/tty7/pull/43)(Fixes' detected everything
through '(Fixes' as one URL. Meanwhile trim_trailing_punct stripped a
trailing ')' unconditionally, so a balanced Wikipedia-style
'…_(programming_language)' lost its real closing paren.
Treat closers by balance instead, both ways:
- cut the URL at the first ')' or ']' with no matching opener inside it
(the '(' before the scheme is prose, dropped with the prefix), and
- keep a trailing ')' / ']' whose opener sits inside the URL, stripping
only unmatched ones (full-width closers can't appear in a URL at all
and stay unconditional).
The www. fallback re-trims after dropping leading wrappers, since
'(www.x)' now keeps its ')' through the first, balance-aware trim.
IPv6 '[::1]' hosts survive the same way.
Co-authored-by: l0ng-ai <24760907+l0ng-ai@users.noreply.github.com>
A replaced daemon used to linger forever: both takeover paths (ensure_running
on a failed connect, restart after the Shutdown wait) would unlink the endpoint
and spawn a fresh daemon without checking whether the old process was still
alive. A daemon that couldn't be stopped — a binary predating ClientMsg::Shutdown,
or a wedged teardown — survived unreachable, still holding every pane's PTY and
children. That is exactly how 14 panes (11 live sessions) got silently stranded
across an app update.
Now the daemon records its pid in <config>/daemon.pid after bind, and both
takeover paths reap the recorded daemon before claiming the endpoint:
SIGTERM first — handled by a new sigwait thread that tears down like Shutdown,
giving every pane's child its SIGHUP grace — then SIGKILL if it won't die.
The pidfile is never trusted blindly: the pid must be alive and its executable
basename must match our own, so a crashed daemon's stale pidfile (pid possibly
recycled) is cleared, not killed. Windows reaps via the existing winproc
helpers, descendants-first, mirroring the pane hangup order.
Fixes#42
Co-authored-by: l0ng-ai <24760907+l0ng-ai@users.noreply.github.com>
Selecting with the mouse — drag, double-click word, triple-click line,
over terminal output or the prompt's command editor — copies the
selection the moment the gesture ends, no Cmd+C needed. Opt-in via
Settings -> Terminal -> Clipboard (config key copy_on_select), off by
default so a stray selection never overwrites the clipboard.
Closes#34
Co-authored-by: l0ng-ai <24760907+l0ng-ai@users.noreply.github.com>
* fix(hints): dismiss held-modifier tab badges on window activation flip
The badges were dismissed only by ModifiersChanged (release) or a real
keypress. Deactivating mid-hold — cmd-tabbing away, Spotlight, a click
into another app — delivers the modifier release to whatever app is key
by then, so this window never saw it and the badges stuck on until some
later keypress; mouse-only use left them up forever.
Dismiss on every window-activation flip via observe_window_activation.
Flipping on *both* directions also cancels a reveal scheduled just
before the switch, so the timer can't pop badges up in a window the
user already left.
Tested with a headless gpui harness: Tty7App now builds through a
with_session seam (a zero-tab session restores the home page, spawning
no terminal/daemon), the test activates the window for real, simulates
the bare-secondary hold, then deactivates and asserts the badges (and a
pending reveal) are gone. Session's save/load test takes a shared file
lock so parallel tests can't clobber the pinned session.json.
* style(hints): satisfy rustfmt in gpui test harness
---------
Co-authored-by: l0ng-ai <24760907+l0ng-ai@users.noreply.github.com>
Dragging a selection above the top (or below the bottom) of the pane
used to pin at the visible edge: pos_to_cell clamps the row and
on_select_update never moves the display offset, so scrollback content
just out of view was unreachable by mouse selection.
Now a left-drag that crosses the pane's vertical edge arms a repeating
50ms task that scrolls the display and re-anchors the selection's
moving end to the edge row, so the scroll continues while the pointer
holds still past the edge (mouse-move events alone stop the moment the
hand does). Speed scales with the overshoot distance — 1 line/tick when
grazing the edge up to a capped 8 lines/tick (~160 lines/s) — and the
task stops when the pointer re-enters, the drag ends, or a newer task
supersedes it (epoch guard, so a quick leave/re-enter/leave can't
double the speed). On the alt screen scroll_display no-ops (no
scrollback), leaving TUIs untouched.
Co-authored-by: l0ng-ai <24760907+l0ng-ai@users.noreply.github.com>
Windows: a shell that exits on its own (exit / Ctrl-D / crash) left the pane
wedged open because ConPTY's output pipe never EOFs on a natural child exit.
Add a Windows-only exit monitor that waits on the child handle directly and
reports through a shared, run-once DeathReporter latch — the same Exited /
on_dead path the reader's EOF drives on Unix. Unix behavior is unchanged.
Claude-Session: https://claude.ai/code/session_01ABey161AUxhgmJC3PRoYtF
* fix(terminal): give solo prompt glyphs a two-cell paint window
Nerd Font prompt icons and the multiline `➜`/`❯` rendered sliced off on
the right (#17). tty7 paints any single-column glyph clipped to exactly
one cell, but a symbol face routinely draws ink past that cell: a
non-Mono Nerd Font sets a one-cell *advance* on its icons yet fills up to
~1.9 cells (measured across Hasklug / Meslo / JetBrainsMono NF), and the
OS cascade serves a proportional `➜` the same way when nothing in the
font list covers it. The per-cell content mask then severed the overflow
— the incomplete icons and cut-off arrow in the report.
Advance is no signal here (it reads one cell for those overflowing
icons), so widen every solo glyph's clip to two cells. A glyph that
already fits is untouched — it has no ink to spill — while a symbol that
overflows renders whole, bleeding into a trailing blank the way iTerm2
and Terminal.app do with non-Mono faces; the two-cell bound keeps a
pathological face from smearing a lone glyph across the row. Batched
Run/Wide segments keep their exact column-span clip, and Mono Nerd Fonts
are pixel-identical. Pairs with the native powerline separators from #19.
Verified in-app on Apple Silicon with a non-Mono "Hasklug Nerd Font"
primary: icons and a powerline prompt that were severed now render whole,
with no change to ASCII, CJK, or the Mono-font path.
* test(terminal): cover both clip-width branches, not just the solo constant
Fold the solo/batched clip-width decision into one pure `seg_clip_width`
so the branch itself is testable (per PR review): the test now asserts a
batched Run/Wide segment clips to its exact column span while a solo
glyph gets the two-cell window, instead of only checking the standalone
constant. No behavior change.
---------
Co-authored-by: l0ng-ai <24760907+l0ng-ai@users.noreply.github.com>
gpui-component's MenuItemElement highlights the hovered/selected row from
`tokens.accent` + `accent_foreground`. tty7's theme never overrode those, so
menu rows fell back to the stock saturated accent — a hard highlight out of
step with the app's soft mix-based palette.
Point the accent tokens (and the plain `accent`/`accent_foreground` fields the
input completion / code-action popovers read) at `list_active` (mix 0.17), the
same soft fill the command palette uses for its selected row, and keep the
hover text at `foreground` so it stays legible on the low-contrast fill. Right-
click menu, "+" dropdown and command palette now share one hover language.
Claude-Session: https://claude.ai/code/session_01ABey161AUxhgmJC3PRoYtF
Co-authored-by: l0ng-ai <24760907+l0ng-ai@users.noreply.github.com>
Opening a new tab or split stalled for seconds while a zsh plugin manager
reinstalled itself from scratch. tty7 launches zsh through a per-pane throwaway
ZDOTDIR so it can layer its OSC 133 shell integration on top of the user's
config, but it left ZDOTDIR aimed at that empty temp dir for the whole shell
lifetime. Tools that resolve their own state via ${ZDOTDIR:-$HOME} — Zim
(.zimrc/.zim), oh-my-zsh, compinit's .zcompdump — therefore looked inside the
empty throwaway dir and rebuilt on every pane: Zim redownloads/recompiles every
module (the ~3s stall and "modules/…: Installed" spam of #15), compinit rewrites
its dump into the temp dir, etc.
Each redirector now stashes our dir, points ZDOTDIR at the user's real config
dir while their startup file runs, then restores ours so zsh still reaches the
next redirector; the integration body restores the real dir for the live
session via a one-shot precmd hook. When the user never set a ZDOTDIR we unset
it (rather than force $HOME) while sourcing, so their file sees exactly what a
real launch gives it and the `: ${ZDOTDIR:=~/.config/zsh}` relocate idiom still
fires. .zshenv recaptures a user-relocated ZDOTDIR so the classic tiny-~/.zshenv
layout keeps both its config and tty7's integration (previously it silently lost
the integration).
Verified end-to-end against real zsh for three layouts (config in $HOME,
unconditional relocate, and `-z`-guarded relocate): a second pane now hits the
plugin manager's cache instead of reinstalling, and runtime ZDOTDIR resolves to
the real dir. Adds three unit tests covering the redirector ordering, the
.zshenv recapture, and the runtime restore hook.
Fixes#15
Co-authored-by: l0ng-ai <24760907+l0ng-ai@users.noreply.github.com>
A remote shell that emits its own OSC 133 prompt marks — fish 4.x over
ssh, most visibly, which ships them on by default — made the daemon
report at_prompt=true, so tty7 engaged its local line editor and Tab ran
completion against the local filesystem instead of reaching the remote
shell.
The shell tty7 spawned only emits its OSC 133 marks while it is itself
the PTY's foreground process group (idle at its prompt). Reject a prompt
mark whenever a foreground command (ssh, a TUI, a nested shell) owns the
PTY — comparing the PTY's foreground pgid against the shell pid — so
keystrokes pass raw to whatever is really reading them. The normal local
case (shell idle ⇒ it is the foreground group) is untouched.
Fixes#26.
Claude-Session: https://claude.ai/code/session_01ABey161AUxhgmJC3PRoYtF
Promote the [Unreleased] changelog to [0.6.0]: the "+" shell picker and the
Windows PowerShell 7 default, plus this cycle's fixes — native powerline
separators + bundled Hack fallback (#17), URL detection stopping at full-width
brackets (#20), PATH-probed shell detection for brew/nix installs (#18), and
stale-daemon restart-and-retry on upgrade (#22). Bump the crate version
0.5.0 → 0.6.0 so the built binary reports its real version to the update check.
The Unix shell picker only enumerated $SHELL and /etc/shells, but
Homebrew and nix don't register what they install there (Homebrew only
suggests adding fish to /etc/shells, and few users do). A brew-installed
fish therefore never appeared in the "+" dropdown, even though the app
can spawn and shell-integrate it fine once selected.
Probe a curated set of well-known shells (fish, nu, pwsh, elvish, xonsh)
on PATH as a catch-all, after the /etc/shells entries. Registered shells
keep their /etc/shells paths via the existing basename dedupe; only the
unregistered leftovers are picked up from PATH. Uses the login-shell-
enriched PATH so Dock launches see Homebrew's prefix too.
Closes#18
Co-authored-by: l0ng-ai <24760907+l0ng-ai@users.noreply.github.com>
A URL followed by a full-width parenthetical with no ASCII space, e.g.
`…/pull/343(fix/… → dev)`, leaked the bracket text into the link: the
whitespace split ran past the URL and trailing-punctuation trimming only
touches the token's end. URL characters are all ASCII (RFC 3986), so after
locating the scheme, truncate at the first non-URL char — a CJK character,
full-width bracket, arrow or emoji — which marks where the link ends. Interior
ASCII parens (Wikipedia-style URLs) are preserved. Adds regression tests.
Co-authored-by: l0ng-ai <24760907+l0ng-ai@users.noreply.github.com>
Prompt themes (powerlevel10k, oh-my-posh, oh-my-zsh) render their
segment separators and status glyphs through whatever face the font
fallback chain resolves, and two failure modes followed (#17):
- Powerline separators (U+E0B0-E0BE) resolved from a fallback face
render at *that face's* advance, not the cell width — narrow wedges
that no longer meet their segment backgrounds, or tofu with no Nerd
Font installed at all.
- A glyph nothing in the configured chain covers falls through to the
OS cascade, which can serve a proportional glyph wider than the
cell; paint_glyphs' per-cell clip then truncates it — the severed
U+279C arrow in the report (pixel forensics: the cut is a zero-
antialiasing vertical edge exactly one cell after the glyph origin).
Fix, the approach Warp (stretchable SVGs) and kitty (programmatic
glyphs) settled on:
- The eight solid powerline separators — sharp triangles E0B0/E0B2,
round caps E0B4/E0B6, slants E0B8/BA/BC/BE — are now drawn as gpui
fill paths sized to the exact cell: pixel-perfect for every
font/size/line-height combination, no font dependency at all. The
thin/outline variants stay on the font path (hairline strokes can't
be a fill, and the bundled Hack covers the common ones).
- fallback_chain() appends the bundled "Hack" to every font fallback
list, so prompt symbols (U+279C, U+276F, box drawing, the sharp
wedges) resolve against a monospace-fitting face before ever
reaching the OS cascade lottery.
Claude-Session: https://claude.ai/code/session_01ABey161AUxhgmJC3PRoYtF
Co-authored-by: l0ng-ai <24760907+l0ng-ai@users.noreply.github.com>
The background detect_shells() update set detected_shells without
cx.notify(), so an idle window kept rendering the pre-probe (empty)
list until something else forced a redraw. Flagged by CodeRabbit.
Claude-Session: https://claude.ai/code/session_01ABey161AUxhgmJC3PRoYtF
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