- New ui::dialog module holds the shared modal chrome, taken from the
workspace switcher: a 12px card, a 48px title row with an esc keycap,
18px insets, a 40px hairline footer, 28px borderless field wells on the
faint fill, 11.5px medium muted labels, and 18px keycaps.
- Buttons: the primary is the inverted neutral fill, the Commit button's
paint, instead of the accent. Secondary buttons are transparent with the
surface's hover rung. Override on a changed host key stays the one red
button. A disabled button sinks to the faint fill and drops its click
handler.
- SSH sheet: host and fingerprint lines sit in a mono detail well, and
keyboard-interactive prompts become field labels. Banners match the
sheet's width and card shape.
- Worktree prompt: moves to the same card, with the path preview hung off
the Name field.
- Notice pill: severity moves from a tinted edge to a 6px leading dot.
- Home: shortcut rows are 28px with a hover fill and keycap chords, and the
remote strip's action uses the secondary button.
Carry the v4 language into the diff overlay and the commit detail view:
0.5px hairlines at 8% ink, 26px row pills with a 6px radius, the rem type
ladder from right_panel.rs, neutral chips instead of accent washes, the
shared git_badge for status letters, and tabular figures on counts.
Layout, spacing, type and colour only; no behaviour or i18n changes.
- Rows pad 8px inside lists inset 12px, so text sits on a 20px column in
every tab and hover fills start 12px in with a 6px radius. Headings,
empty states and the Ports line move to the same column.
- Tab labels 18px apart; the panel row's chrome tiles are 26px, 4px
apart, 12px from the edge. Default panel width 280.
- Info: label column floor keeps values at x=88; Ports add tile 22px.
- Changes: 8px top gap on macOS, pinned block on 14px edges with the
branch at 22, 12px sync glyph, 8px group chevron, 10px status cell,
1px between rows.
- History: compact gutter for single-lane pages, filtered rows on the
text column, 10px row gap, 24px age floor, header on 20px insets,
4/12 padding when expanded (heights re-counted in commits).
- Files: search well at 12px with an 11px glyph, 10px before the tree,
16px indent step, 16px bottom padding.
- Default Light/Dark take warm neutrals (#fcfcfb/#1c1c1e, #18181a/#ececed);
Git added/modified seeds follow v4 green and amber.
- The left rail gets its own tinted fill again (Neutrals.rail, 3% toward
the ink) with its own surface ladder; the right panel keeps the content
fill. Captions and hairlines are floored on the rail too.
- Title bar is 48px; the bar over the terminal centres the active tab's
title in caption ink when tabs live in the rail.
- Rail header: 26px new-tab and collapse tiles, then the workspace chip
and search field (28px, 7px radius).
- Groups sit 16px apart under a 22px caption heading with
'branch · +a −d' in tabular numerals.
- Rows are 30px (42px with a branch line), 16px avatars, medium weight
when current, branch cut from the front, and a trailing 5px status dot
(blinks while working, hollow while waiting, unread count as a pill).
- docs/design-system.md updated.
- Pinned block keeps 8/10/14 rhythm; branch name medium, 26px sync tile.
- Commit message box rests at 56px with a 7px radius.
- Split commit control: inverted neutral fill when committable, faint
fill otherwise; 6px radius and an inset 0.5px seam.
- Change groups sit 16px apart under 22px sentence-case medium headers;
file names take width first and directories right-align, eliding
from the start.
- History: 32px header, 26px rows inset with rounded hover, 1px lines
and 7px beads (HEAD filled, others hollow), neutral inks on a
single-lane page, age column always shown, faint HEAD pill.
- Card: 112px from the top, 12px corners, 48px search row with an esc
keycap, 420px body split 340px / preview, 40px footer.
- Workspace rows are 52px: a 26px initial disc carrying the link state as
a ringed dot (live green, faint when offline, amber while connecting,
red on failure), a medium name with its stable number, a machine ·
path · time line, and the tab count over the state word.
- Preview rows are 44px with the sidebar's 18px brand disc, an all-muted
branch · diff line, a Current label and a 5px dot that blinks with the
sidebar while an agent is working.
- Footer: ghost New workspace button and keycap hints for navigate, open
and new window; the unused click-for-new-window string is dropped.
- Tab row: 12.5/16rem word tabs 22px in and 18px apart, the current one in
body ink at medium weight; no hover pill, no underline bar, no hairline
under the row.
- Info: Session, Processes and Ports are spaced 16px apart with no rules;
28px medium muted headings, 28px Session rows on a 76px label floor with
values in body ink, 26px process rows with a tree elbow for children, and
an explicit empty line for Ports.
- Files: the search sits in a 28px filled well; tree rows are 26px with a
disclosure chevron column, ignored entries dim their icon instead of
going italic, and a folder's change dot is 5px.
- docs/design-system.md updated to match.
A working agent's dot now alternates every 600ms between its colour and
a paler, still opaque, blend of it over the dot's ring, so a column of
tabs shows at a glance which agents are still going.
It rides the existing half-second home-cursor tick rather than an
animation: two repaints a second while some tab has a working agent,
and none once no agent is working (the dot settles back on its full
colour with one last frame).
On Windows and Linux the tabs sit under a full-width title bar, and a
second 40px band left them well down from the window's top. The row is
now the tab's hover pill plus 2px either side.
Settings used to cover the workspace it was opened from, hiding the
terminal a setting was being tried on. It now opens a separate window.
- The state and page stay on the workspace's Tty7App; a thin
SettingsWindow view borrows it to draw, observes it to repaint, and
sets its own rem size. The state is built against the new window, so
its inputs and subscriptions belong to it.
- Opening again raises the window instead of closing it. Esc, Cmd-W and
the close button go through close_settings_checked, so unsaved forms
and theme drafts still prompt; closing hands focus back to the
workspace window. The window goes when its owner is released.
- open_settings_then / close_settings_then run follow-ups in the right
window: loading an SSH form happens in the settings window, and
Connect opens its tab in the workspace window.
- Window opacity, blur and backdrop are pushed to every window, since
they are now changed from a window that is not the workspace.
- Tests keep the in-window overlay so they can drive settings through
their single test window.
Folded, the history section is the last line in the window and sat flush
against the bottom edge, inside the window's rounded corner. Pad it 2px
above and 4px below; the expanded section is unchanged, so its
commits-per-height pin still holds.
The v5 restyle turned the tab rows, the search field and the workspace
switcher into full pills, which read too round at this density. Put
back CARD_RADIUS on the rows, rounded_lg on the search field and
rounded_md on the switcher (and its rename field), keeping the 28px
row height. PILL_RADIUS has no users left and goes with them.
The workspace switcher painted its picked row a hard-coded #1768CF with
white text, in the workspace list, the tab list and the New Workspace
host dropdown. It bypassed the theme and was the loudest block on
screen. Use the popover's neutral selected step and keep each row's own
inks, so a taken-over warning still reads as one.
- Rails share the window fill and are separated by a hairline only;
the current tab is a neutral selected step plus a semibold title
instead of the blue navigation wash (the unused role is removed).
- Default Light theme takes ink #0F1419 and accent #1F6BF0; the caret
follows the accent.
- Sidebar: 28px pill rows with 18px avatars (two-line rows keep card
corners), pill search field and workspace switcher, group headings
in body ink with the chevron shown only when folded.
- Right panel: word tabs (Info / Changes / Files) with an underline
bar on a closing hairline. The Changes count is dropped before any
label is cut, and the panel floor grows with the measured labels so
the chrome tiles are never pushed off the edge.
- Headings are no longer uppercased; popovers use a 10px radius.
- docs/design-system.md updated to match.
On macOS the panel toggle and app menu tiles sat at the end of the terminal
column's strip whenever the detail panel was closed, including when a docked
diff or file column stood to its right, leaving them in the middle of the
window beside the document's header. Hide them while a document is docked and
stop reserving their width for the tab chips.
Three things went wrong with a Files panel rooted on a distro's \\wsl$
share:
- A drop brought a `name:Zone.Identifier` file along with every
downloaded file. fs::copy is CopyFileEx on Windows, which copies the
NTFS alternate data streams, and the share has no streams to keep one
in, so it lands as a file of its own. A copy onto a WSL share now
moves the contents only; one onto NTFS still goes through fs::copy
and keeps the mark.
- A file removed from the shell in the distro stayed in the tree. The
share accepts a ReadDirectoryChangesW and never reports a change, so
the watch looked healthy and was deaf. Directories on a WSL share are
now watched by a notify PollWatcher every 2s instead, made the first
time one is needed.
- Right-clicking a row did not select it, so nothing marked the row the
menu was about. A right click now selects the row without opening it.
Dropping a RemoteWatch sent WatchClose with a blocking call, and the last
handle is usually let go on the UI thread: from Tty7App::render via
scm_sync_watchers, and from scm_watch_opened when an open lands after its
subscription moved on. Every window froze for a round trip each time, up
to WatchClose's 5 s deadline on a quiet link. Sampling a live instance
caught ~1.1 s of such stalls in 10 s.
- ControlClient::post sends a request without registering for its reply;
the reader already drops replies nobody is waiting for.
- RemoteWatch::drop posts WatchClose instead of calling it.
- pane_workspace_for built the full SSH spec, keychain lookups included,
only to strip the secrets again. It now builds it from NoCredentials,
which takes the securityd trips off pane_liveness::sweep.
Claude-Session: https://claude.ai/code/session_01YX786Hyr4ivijWxv66zVkf
The reader parsed each batch — and a relink's whole replay, up to 8 MB —
in one hold of the grid lock, and every UI-thread handler that touches
the grid queues on it. Feed the emulator in MAX_LOCKED_FEED slices, the
same bound Alacritty's reader uses.
Claude-Session: https://claude.ai/code/session_01YX786Hyr4ivijWxv66zVkf
A portable ZIP install whose folder has both the .tty7-portable marker
and a data\ folder next to tty7-app.exe now uses <exe dir>\data as its
config directory, so settings, sessions, scrollback and the daemon's
socket travel with the program. --config-dir and TTY7_CONFIG_DIR still
win. The folder is an explicit opt-in because the marker has shipped in
every portable ZIP since the in-app updater arrived: keying on it alone
would move existing users onto an empty directory at their next update.
The portable directory is not the machine's config dir, so a portable
copy never adopts the installed tty7's legacy tree. An updater test pins
that data\ is not a managed root and survives an in-place update.
SelectWorkspace1-9 and the Window menu numbered workspaces by most
recent use, so switching to one moved it to slot 1 and reshuffled the
rest. Number them by the order this client first had them instead
(the append-only views list), skipping synced remote references until
they are opened; opening one moves it to the end so it takes the next
free number. The switcher keeps its MRU list and shows each row's
number.
With a long prompt the inline editor started at the prompt's end column,
leaving e.g. 24 of 143 columns for the first row of input. When fewer than
max(20, cols/3) columns remain (and the prompt is at least as wide as what
it left, so a new row actually gains room), the input overlay now starts at
column 0 of the row below the prompt.
The start position is decided by one pure fn, input_start(), and fed to
everything that lays out the input: the overlay rows and overflow shift,
click-to-index mapping, vertical caret movement, the rendered bar, the
completion menu anchor and the IME candidate anchor.
Add a macOS-only `font_thicken` key (default true) and a Settings row
under Appearance > Terminal text. When off, AppleFontSmoothing is pinned
to 0 in this process's NSArgumentDomain before gpui's text system first
reads it, so glyphs render at the face's own weight. The volatile domain
is in-memory only: nothing is persisted and no other app is affected.
gpui caches the preference in a OnceLock, so a change applies after a
restart; no gpui fork change is needed.
A remote workspace's Files tree took uploads by drag-and-drop but had no
way to bring a file back. Right-click a file -> Download now reads it
through Host::read_file and saves it into this machine's Downloads
folder, named and numbered the way the SFTP panel's Download does.
Capped at the same ~63 MB as uploads (one control frame); an oversized
file is refused up front with the limit and nothing is transferred.
Local trees keep Reveal in that slot; folders are not offered.
Closes#723
Cursor's ~/.cursor/hooks.json is a flat hook map — `command` directly on
each entry under `hooks.<event>` — so it reuses the flat writer Crush
introduced. Two things are Cursor-specific:
- The file needs `"version": 1` at its root or Cursor ignores it. tty7
now writes it when it creates the file or finds it missing, never
overwrites a version the user set, and reports a versionless file that
holds our hooks as Outdated so refresh repairs it.
- Payloads name the session `conversation_id` (only sessionStart repeats
it as `session_id`), and most events carry `workspace_roots` instead of
`cwd`. Both are now read as aliases, which is what Copy Session ID and
resume were missing.
Events: sessionStart, beforeSubmitPrompt, postToolUse, stop, sessionEnd.
cursor-agent does not fire beforeSubmitPrompt today (a known gap on
Cursor's side), so postToolUse also reports prompt-submit: the first tool
call opens the turn and Cursor's stop, which the CLI does fire, closes it.
A turn with no tool calls never shows as working. None of Cursor's
permission hooks are installed, since those would block on the empty
stdout tty7's hook prints.
Add SelectNextTab / SelectPrevTab, which move to the neighbouring tab in
the order the strip or sidebar shows them, wrapping, with no popup.
Defaults: Cmd+Shift+] / Cmd+Shift+[ on macOS, Ctrl+PgDn / Ctrl+PgUp
elsewhere. The tmux preset's prefix n / p now bind these (tmux
next-window semantics) instead of the MRU switcher, which never
auto-committed there.
NextTab / PrevTab keep their config names and Ctrl+Tab, but are labelled
Recent Tab Switcher on the Keybindings page; the palette's Next/Previous
Tab entries now show the SelectNextTab chord they actually run.
The code panel's Preview/Edit and Wrap buttons only toggled state in their
click handlers, so there was no way to reach them from the keyboard. Register
both as actions, unbound by default like ToggleDocumentFill and the
DocumentWidth* actions, list them in the command palette and the Keybindings
page, and route the buttons through the same methods.
Instead of config keys for the initial state, the last-used state is
remembered (editor_soft_wrap / editor_markdown_preview), the way diff_view
and scm_graph_expanded already are. A file opened at a line target always
opens as source so the cursor is visible.