mirror of
https://github.com/l0ng-ai/tty7.git
synced 2026-10-04 00:02:04 +00:00
4e8a76261b01ac1a50c7494afe82e486ea3c6d92
122
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
ee817bae01 |
feat(ui): v5 modal cards, and an IME crash fix in the code editor (#993)
* fix(deps): gpui-component keeps highlight boundaries on char boundaries Typing Chinese through the IME in the code editor could panic the app: after a sync parse ran past its 2ms budget, the stale injection layers put a style boundary inside the committed glyph and the text system split the line mid-character. Debug builds miss the budget often enough to hit it; the fork now snaps every style range to the current text. * feat(ui): bring the modal cards onto the v5 design - Confirmations take v5's alert shape: title and detail as one paragraph, answers beneath, no header row or footer rule, 360px wide and set lower, at eye height. - Dialog buttons take the field's 7px corner and 14px padding; a secondary answer rests on the well's faint fill instead of bare text. - The scrim dims rather than blacks out: 45% dark, 14% light. - The New Workspace form joins the other cards: 12px corner, borderless wells for its fields, an esc cap and the shared 40px footer. |
||
|
|
f647a19746 |
feat(editor): draw the editor's right-click menus like the rest of the window (#992)
* feat(editor): draw the editor's right-click menus like the rest of the window The text's menu was gpui-component's native OS menu: another font and material, no shortcuts, and Go to Definition / Show Code Actions that no language server ever enables. It is now a PopupMenu with Attach to Agent (the selected lines ride along as #L3-9), Undo/Redo, the clipboard, Find, Go to Line, and the file's own items: Open in Browser for web files or Open with Default App, Reveal, Copy Path, Copy Relative Path. The file tabs get a menu too: Close, Close Others, Close to the Right, and the same file items. Closing several asks once about unsaved edits. * chore(deps): gpui-component moves the caret on right-click under a host menu |
||
|
|
bc08fc49f4 | fix(deps): gpui-component paints text selection under the glyphs (#991) | ||
|
|
23e4ea79f5 |
chore(deps): gpui-component with v5 menus
Picks up l0ng-ai/gpui-component bce0d8ec: popup and context menus take the v5 popover look — a neutral hover step, 28px rows, a hairline ring and v5's long shadow. Claude-Session: https://claude.ai/code/session_01Aeskbok7Ah7dvKWnaaJFEg |
||
|
|
bf5149bea0 |
fix(editor): stop losing edits, share buffers, add file strip, quick open and go to line (#984)
* fix(host): save local files atomically via a temp file and rename LocalHost::write_file truncated the target in place, so a crash, a full disk or a killed process mid-save destroyed the user's file. It now writes a hidden sibling temp file, syncs it, keeps the old file's mode and renames it over the target, removing the temp file on any error. It still writes in place where a rename would change something visible: a non-regular target (symlink, directory, FIFO), a read-only file, and on Unix a hard-linked file or one owned by another user, or when the temp file cannot be created (e.g. a read-only directory). * feat(editor): add editor_text for encodings, line endings, indentation and EditorConfig A pure module the code editor will use when loading and saving files: decode detects BOMs, binary files, UTF-8, GB18030 and a lossless Windows-1252 fallback and normalises CRLF; encode restores the exact bytes and names the first unrepresentable character; detect_indent infers tabs or a 2/4/8 space width with language defaults; and editorconfig_for resolves .editorconfig sections with save-time rules. * feat(editor): share buffers across tabs, guard unsaved work, add a file strip - One buffer per file per window; tabs list which buffers they show. The same file open in two tabs is no longer two diverging copies. - Closing a tab, its last pane, the window, or quitting asks about unsaved files (Save / Cancel / Discard) instead of dropping them. Bulk closes skip tabs with unsaved files; a tab that vanishes any other way hands its unsaved buffers to the tab in front. - File tree rename/delete now retarget or flag the open buffer, so a save no longer recreates the old path. - Saves check the file's mtime first and ask before overwriting a change made elsewhere; this is the only detection SFTP buffers get. - Dirty is a comparison with the saved text, so undoing back clears it. - Reloads replace only the changed span as an ordinary edit, keeping undo. - Load/save go through editor_text: encoding, BOM and CRLF round-trip, indentation is detected, .editorconfig is honoured. - Header shows a strip of open files; New File, Save As (native panel locally, a path bar remotely), Go to Line (Ctrl+G), and the status bar shows indentation, encoding and a clickable line ending. - Open files are remembered per tab across restarts. * feat(search): quick open a file by name from a Files tab Search Everywhere gains a Files tab that finds any file in the active tab's project by fuzzy name and opens it in the built-in editor, with `name:line[:col]` jumping to that spot. The list comes from one walk of the project through the host (Host::search with an empty query), so it works the same on local, SSH and WSL workspaces and skips what the tree hides: dotfiles, .git and gitignored paths. The walk is capped at 50k entries / 20k directories, kept between openings and revalidated in the background each time the search opens. Files join the All tab once a query finds them. Go to File... is bound to Cmd+O on macOS (Cmd+P is already Search Everywhere) and ships unbound elsewhere, where every obvious chord is taken or owed to the shell. * chore(editor): allowlist the editor session file, tidy lints * fix(editor): keep restored file order, drop stale close waits, carry files through tab merges - Background arrivals (restore, merge, rescue) append to the strip in order instead of inserting beside the active file, which reversed them. - A cancelled Save As, a dismissed path bar, or a dropped buffer cancels any close that was waiting on that save. - Merging a tab into another carries its open files along. - A shell exiting closes its tab without a prompt it could not honour; unsaved buffers move to the tab in front. - Tabs rebuilt under the same id (server restart) restore their files. * fix(host): only fall back to an in-place write when the rename is refused On Windows every failure of the atomic save fell back to fs::write, including a failure while staging the temp file. A full disk would then truncate the original in place, the very loss the temp file prevents. Staging errors now return as-is; only a refused rename (a file held open elsewhere) takes the in-place path. |
||
|
|
fd2c4f7d4e |
feat(panel): Search and GitHub tabs in the right panel (#978)
* feat(panel): add Search and GitHub tabs to the right panel The right panel grows from three tabs to five. Five word labels do not fit the panel's 280px resting width, so the tab row now draws a glyph per tab and names it in a tooltip. Both new panes are placeholders here; the content search and the GitHub issues/PR browser land on top of this. * feat(panel): find in files in the right panel's Search tab The Search tab replaces its placeholder with a content search over the active tab's project -- the same roots the Files tab shows -- on the host that project lives on. Hits arrive as you type (debounced, with a generation counter so a stale answer never lands), grouped by file with a count, each line excerpted with its matches highlighted. Clicking a hit opens the built-in editor at that line and column; Enter searches again. Match-case, whole-word and regex toggles sit at the end of the field, and the tab focuses its field whenever it is brought forward. Host::search_content is new on the Host trait, implemented once in host::content_search (ignore walk + regex) and run by LocalHost directly and by tty7-server over a new SearchContent control request. The walk honours .gitignore with or without a repository, skips dot-entries, binary files and files over 1 MB, and reports a capped search as truncated. The request is gated on a new `content-search` hello feature, so a server that predates it is never sent it; the panel says the server needs updating instead of showing no results. Conformance cases cover local and the stdio server alike. * feat(panel): browse GitHub issues and pull requests in the right panel The GitHub tab follows the focused pane's repository: its root is resolved the way the Source Control tab does, its remotes are read through the Host (the tree may be on another machine), and the github.com remote is bound, upstream over origin in a fork, with a menu to pick another. The list switches between issues and pull requests, open and closed, 50 rows a page with Load more; rows carry a state glyph distinct by shape, labels (click one to filter by it) and relative times. A row opens the detail in place: title, state, author, labels, description and comments as Markdown, and for a pull request its branches, size and changed files. A file opens in the diff overlay through a new supplied-patch DiffSource, so GitHub's patch renders exactly like a local one without a git probe. Read-only, and sign-in reuses the GitHub CLI: GH_TOKEN, GITHUB_TOKEN, then `gh auth token`, found on PATH or at the Homebrew locations a Finder launch cannot see. Signed out, public repositories still work; 401, 403, 404 and rate limits are told apart and explained. Requests go out from this machine over the installer's ureq stack and proxy settings, on threads of their own, cached per repository with background revalidation. Remote images in issue text become links instead of loading, and non-web link targets are disarmed. The Info tab gains a GitHub row that opens the branch on the remote it tracks, or the repository for a branch never pushed. * docs: list ShowRightPanelGitHub with the other panel actions * feat(panel): one-line GitHub rows, a pill for the current tab - GitHub list rows are one line: state glyph, #number, title. Labels and the age of the last update appear on hover, from state rather than a group_hover display switch, which gpui cannot paint. - The current right panel tab sits on the sidebar's selected fill; ink alone could not tell five same-weight glyphs apart. - The GitHub glyph is a 1.8px outline like the other tab icons, not the filled mark. - The detail byline names both times (opened / updated) so it no longer reads as disagreeing with the list's update age. * feat(github): show screenshots pasted into issues Images GitHub hosts itself (github.com/user-attachments, a repo's /assets, *.githubusercontent.com) now render in issue and PR text, each in a paragraph of its own so the text view draws it at its size rather than at line height. Images from any other host stay links, so opening an issue still tells no third party that you read it. gpui held a null HTTP client, so no remote image could load; the app now installs the update check's reqwest client (same user agent and proxy) at launch. * fix(github): load private-repo screenshots, give inline code a neutral fill - Pasted attachments (github.com/user-attachments/assets/<uuid>) want a browser session on a private repository, which an API token is not. The detail and comment requests now ask for the full media type, and each attachment is swapped for the signed private-user-images URL the rendered body_html carries for the same uuid. - Inline code in rendered Markdown (the GitHub tab and the editor's preview) sits on a faint neutral fill instead of the theme accent, which is also the selection colour. Needs gpui-component 6af19d91 for TextViewStyle::inline_code_background. * style(panel): tidy the GitHub and Search tabs' top rows - GitHub drops its heading row on macOS. It existed only to hold the refresh tile, and no other tab has one; refresh now sits with the repository's other actions, in the repo row and a detail's header. - Search's Aa / ab / .* toggles are muted while off instead of body ink. - Search's idle note puts the folder on its own line, spelled ~/…, so the narrow column no longer breaks the path at a slash. * feat(panel): order the right panel's tabs Info, Files, Search, Changes, GitHub Info stays first as the default and the pane's overview; after it come two pairs, the project's files (Files, Search) and its version control from local to remote (Changes, GitHub), where Changes and GitHub were split by the file tabs before. The palette, the Keybindings list and the docs follow the same order. * style(panel): drop the change count from the Changes tab Beside one glyph of five, the number read as a badge on that tab alone, and the Changes tab already leads with the same count under its own heading. right_panel_tabs no longer needs the row's width, which it only measured to decide whether the count fit. * style(icons): fit the GitHub glyph to the other tab icons' size The Lucide mark filled its whole 24px box, edge to edge, where tty7's own icons keep about 3.5px clear, so at 15px it drew a size larger than the four tabs beside it. Scale it to 0.9 about the centre, and raise the stroke to 2.0 so it still renders at the others' 1.8. * style(icons): a simpler GitHub glyph Drop the Lucide mark's tail and redraw the head and legs on tty7's own grid: the same ~15px live area and 1.8 stroke as the other tab icons, no scale transform. The legs keep it reading as the Octocat; a head alone read as any cat. * test(github): find gh on PATH in the blank-variable token test The test placed gh only at /opt/homebrew/bin/gh, which gh_candidates never offers on Windows, so the Windows CI job panicked at unwrap. Put gh on a PATH directory spelled with the platform's exe name instead. * fix(github): close image and link bypasses in the issue Markdown sanitiser Checked against markdown-rs (the parser TextView uses), several inputs got past the line-based rewrite: - is_github_hosted cut the host only at `/`, so `https://evil.io?.githubusercontent.com/x.png` (and `#`, `\`, `/`) counted as GitHub-hosted and was fetched from evil.io. The host now ends at the first of `/?#\` and may hold only DNS characters. - `<img src>` values were written into `` unescaped, so a `)` in the value closed the image and opened a second one from any host. Written destinations are now percent-encoded. - `<image>` (which the HTML parser reads as `<img>`) passed as an ordinary tag and loaded its src. - A kept link target was copied without scanning; when the parser ended the link elsewhere (open title, unbalanced paren) a `` inside it came alive. Markup characters in it are now encoded. - `file:///...` and similar character references passed is_safe_target and decoded to a `file:` link. References are decoded before judging. The rewrite still cannot see every construct the way the parser does (code spans inside tag attributes, fences the parser rejects, multi-line link definitions), so the detail view now also checks the parsed tree: a block containing a non-GitHub image, an unsafe link or definition, or raw `<img>` is drawn as its plain source instead. * fix(github): hide gh's console, bound Retry-After, and reject URL authorities with ?#\ - run gh through proc::output_within with hide_console, so a Windows GUI launch does not flash a console window and stdout is drained while gh runs. - saturating_add a hostile Retry-After instead of overflowing i64. - parse_github_url no longer accepts `https://evil.io#@github.com/o/r`. * fix(search): no panic on an unbounded time budget, and read files through the size cap ContentLimits arrive off the wire on a server; Instant + u64::MAX ms panicked. A file that grew between the size check and the read was read whole; it is now read through a take() at the cap. * fix(panel): keep Load more on an empty filtered page, and drop another host's hits - /issues pages filtered to one kind can come back empty while later pages hold matches; the GitHub list said "No issues" and hid Load more. It now reads on through up to five such pages and keeps Load more offered. - While a new search runs, the previous hits stay on screen; if they came from another host, a click opened their path on the active host. They are now kept only when the host is the same. |
||
|
|
ed939bbc26 |
feat(search): browse every agent's past sessions, locally and on remote workspaces (#977)
* feat(search): list more agents' past sessions, and fork, copy or hide one The Sessions tab listed Claude Code and Codex only, and a row could only be resumed. - Past sessions of Gemini CLI, Qwen Code, Pi, Oh My Pi, Kimi Code, Copilot CLI, Droid, Qoder CLI and CodeBuddy are read from where each keeps them (honouring QWEN_HOME, COPILOT_HOME, KIMI_CODE_HOME, …), with the name the agent or user gave the session, else the first prompt. Context blocks agents prepend (<system-reminder> and kin) no longer hide a prompt. - Cmd/Ctrl-E on a session row opens its actions: Resume, Fork Session (agents that can fork, with the configured launch flags), Copy Session ID, and Remove from List. Removing keeps the search open, drops the row at once and remembers it in `hidden_agent_sessions`; the agent's own history is not touched. OpenCode and Cursor keep sessions in SQLite and are not read yet. Claude-Session: https://claude.ai/code/session_01JRqYZ9E153WpSHGS2AW3BM * feat(search): list remote workspaces' sessions, and OpenCode and Cursor The Sessions tab only ever read this computer, and left out the two agents that keep their history in SQLite. - agent_history moves into tty7-core, and the scan goes through the Host: a local workspace reads this machine in-process, a remote one asks its server (new ControlRequest::AgentSessions; CONTROL_VERSION 11 -> 12, so each remote host takes one Update Server). Resumed or forked sessions open on that machine, in the directory they ran in. The last answer is kept per host so the tab does not open empty. - OpenCode: top-level, unarchived sessions from opencode*.db (or $OPENCODE_DB); a placeholder title gives way to the first prompt. - Cursor CLI: chats under ~/.cursor/chats (or $CURSOR_CONFIG_DIR), named from meta.json or store.db. Cursor files a chat only under the md5 of its directory, so it is placed by matching open tabs' and other sessions' directories; chats nothing matches are left out, since they could not be resumed anywhere. - SQLite is bundled (rusqlite), opened read-only, falling back to an immutable read when the writer's WAL cannot be shared. Claude-Session: https://claude.ai/code/session_01JRqYZ9E153WpSHGS2AW3BM * test(search): wait for the real scan before seeding sessions The search's own scan runs on a real thread. On CI it landed after the test seeded its rows and replaced them, so the edit gesture found no row. The test now waits for the scan first. Assertion messages no longer print session ids (CodeQL rust/cleartext-logging). Claude-Session: https://claude.ai/code/session_01JRqYZ9E153WpSHGS2AW3BM * fix(search): harden the past-session scan and note it in the changelog - Honour CLAUDE_CONFIG_DIR for Claude Code's projects, as the hooks installer already does. - Read a Codex rollout's head as bytes: the 2 MiB cap can split a multi-byte character, and read_line then dropped the whole session. - Escape `%` and `#` (not only `?`) in the immutable SQLite URI fallback, so a database under such a directory still opens. - Refuse a session id starting with `-`: ids now come from file and directory names on disk, and one would be read as a flag by the resume or fork command. - Update the unreleased Sessions changelog entry for the new agents, remote workspaces, the Cmd-E actions and the v12 dialect bump. * fix(search): spell the immutable SQLite fallback as file:///C:/ on Windows SQLite reads file:C:/x as a relative path, so the fallback open (and its test) failed on Windows. An empty authority and a leading slash name the file on every platform. * test(search): keep ? out of the fixture directory name on Windows Windows file names cannot hold a '?', so the fixture's create_dir_all failed there before the fallback was ever opened. |
||
|
|
572bfc014b |
feat(ui): v4 redesign, including a rebuilt settings window (#973)
* feat(ui): restyle the right panel after the v4 design - 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. * feat(switcher): restyle the workspace switcher after the v4 design - 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. * feat(scm): restyle the Changes tab after the v4 design - 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. * feat(ui): restyle the rail and palette after the v4 design - 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. * docs(design-system): note the commit button's inverted neutral fill * fix(panel): align the right panel's insets with the v4 design - 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. * feat(diff): restyle the diff overlay and commit detail after the v4 design 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. * feat(ui): restyle the dialogs, notices and home page after the v4 design - 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. * fix(panel): start Info and Changes flush under the tab row Their first line is text centred in a 28px row, so the extra 8px step put it visibly lower than the Files tab's search well. Only Files keeps it. * fix(scm): put the commit detail on the right panel's 20px text column * feat(palette): restyle the command palette after the v4 design - Card: the switcher's 12px corner, 112px drop from the top (shorter windows still scale it up), 600px max width. - Search row keeps the list's own field; an esc keycap sits in its trailing corner while the field is empty. - Rows are 32px with an 8px corner, 8px list inset and 10px padding. The keyboard row takes the popover's neutral selected step and a medium title instead of the accent wash, via a palette row element in place of ListItem. - Section headings: 28px, 11.5/16rem medium caption ink, on the rows' text column. Shortcuts are per-key 18px faint keycaps from ui::dialog. - New 40px footer with the switcher's keycap hints (navigate, open). - Empty state: headline in body ink, hint in caption ink. * feat(ui): carry the v4 chrome into panes, the file viewer and SFTP - theme: additive helpers for a device-pixel hairline, tabular figures and an inverted neutral button variant. - Pane splits rest as a device-pixel hairline in the divider tone; hover and drag keep the accent at 1px like the other resize edges. - File viewer header: medium file name, 5px unsaved dot, 26px/6px close tile with its glyph on the content inset, divider hairline under it. Status bar: divider hairline, caption size, tabular line/column. - SFTP browser: file-tree rows (26px, 6px corner, 16px caption glyphs), breadcrumb and notes on the 20px text column, a borderless edit well, inverted OK button, and ink-on-track transfer progress. - Forward rows line up with the process and port rows (text at 20px, 6px corner); Add and Reconnect use the inverted neutral fill. * docs(design-system): note the v4 palette, pane, viewer and SFTP chrome * feat(settings): restyle the settings page after the v4 design - Nav: the rail's tinted fill and surface ladder behind a divider hairline; a 28px filled search well; 28px rows in 7px pills, the current one on the selected rung at medium weight instead of the accent; match counts in muted ink; the modified-only filter toggles like a nav row. - Pages: the title sits in the 48px title-bar band at 16/16rem; group headings are 11.5/16rem medium muted on a 28px line; sections are split by a 0.5px divider with 16px either side. - Rows: labels in body ink at regular weight, descriptions at 12/16rem muted, 28px floor with 8px padding; a search hit wears the faint neutral fill rather than the accent tint. - Controls: text fields and dropdowns are 28px filled pills with no outline; buttons, segmented tracks and steppers are 26px with a 6px radius on the same fill. The one primary action per view (save theme draft, connect, install update) is the inverted neutral fill of the commit button. Switches and sliders keep the accent. - SSH: host list header with 26px tiles and a filled search, 22px group headings, 42px two-line host rows; the form's labels are a muted, right-aligned column level with 28px fields; disclosure headers use a chevron on a 28px band. - Theme cards are filled and unoutlined, taking the selected rung while open; the theme panel keeps the content fill with a divider edge and its title in the title-bar band. Keycaps are filled with no outline and shortcut rows are divided by 0.5px hairlines. - right_panel::SECTION_GAP is now shared; docs/design-system.md updated. * feat(ui): spell tab titles out in full in the rail and title bar The rail's rows and the centred title have room to spare, so they take the whole label from a new full_tab_label rather than tab_label's three-segment cut; only the width they have decides what gets elided. * fix(settings): even out the page rhythm and line up the columns - Nav header: drop the min_h(ROW_H)/min_h(0) pair on the heading, which measured ~46pt taller than it painted and opened a hole under the search. - Page titles sit under the title-bar band, level with the nav heading, instead of jammed against the window's top edge. - Headings get a 22pt group-header row and hug their rows; rules keep more air, so a heading reads as its rows' rather than floating between. - SSH: the host list gives width before the nav, so the nav no longer narrows on that page; its header, search well and detail title run level with the nav's; the empty note starts on the host-title column. - Window & Tabs no longer opens on a stray rule. - Integrations: status leads the buttons on one line, in the meta ink. - Terminal: the shell footnote stays close to its rows. * fix(ui): stop eliding branches that fit, and seat the SCM branch on the text column - elide_tail_clusters returned "…" plus the whole string when nothing needed cutting, so the rail's group header printed …feat/v4-redesign with room to spare. Return the text as-is when it fits. - The group header only reserves the chevron's width when it draws one. - The Changes tab's branch name no longer stacks a small button's padding on the row gap; it starts on the file names' column. * fix(ui): keep a tab's name in place when an inline rename starts gpui-component's Input keeps 12px of inner padding even with appearance(false), so the name jumped sideways as the rail row, the group header and the top-strip chip swapped their label for the field. Claude-Session: https://claude.ai/code/session_01Q9vsQSxAZjkwT7nRAiFF1J * revert(settings): restore the page rhythm from before |
||
|
|
d843b82286 | chore(release): v26.9.3 | ||
|
|
6aa83b4bd9 | chore(release): v26.9.2 | ||
|
|
6649cccbc7 |
Merge pull request #822 from l0ng-ai/fix/notification-poll
fix(notify): stop polling Notification Center from the UI thread |
||
|
|
59dbe83913 |
feat(macos): add default terminal integration (#818)
* feat(macos): add default terminal integration * fix(macos): route external opens through the layout pull Five holes in the LaunchServices path, all on the way from a URL to a tab. The `ssh:` arm handed the raw URL back to `parse_quick_connect`, which reads a bare `user@host:port` typed into Quick Connect. Everything a URL carries past the authority landed in the wrong field: `ssh://h:2200/` parsed its port as `2200/` and was dropped on the floor, `ssh://h/srv` became the host `h/srv`, and the percent escapes `url` was added for were never decoded. Read the authority off the parsed URL instead. `x-man-page://3/printf` is Apple's sectioned form, and taking the host as the page name ran `man 3`, which asks the user what page they wanted. Section and page are now both carried. A window that is pulling its layout is one `Adopt::IfEmpty` will not adopt into, so a tab inserted while the pull is out comes back as the whole workspace — the failure `then_open` already exists to avoid. Both the script/man path and the SSH path inserted straight into a freshly restored window, so `then_open` becomes a list of parked requests and carries a command or an SSH link as well as a folder. A cold `ssh://` link also went through `open_at` directly, claiming a fresh workspace and leaving the restored one detached and unannounced; it takes the shared restore now. `new_tab_running` wrote the command whether or not a tab opened, so a failed spawn typed a script path and a newline into whatever pane was focused before — a shell mid-line, or an agent. Left alone deliberately: an `ssh://` link still connects without a confirmation, which is a product call rather than a defect. Claude-Session: https://claude.ai/code/session_01E4EPKzHg1fm9HMmHkUYpER --------- Co-authored-by: l0ng-ai <24760907+l0ng-ai@users.noreply.github.com> |
||
|
|
c9ec23d090 |
fix(notify): stop polling Notification Center from the UI thread
macOS notifications went through mac-notification-sys with wait_for_click so a click could reveal the pane. That crate notices a click by parking the sending thread and adding, per outstanding notification, a repeating 0.5 s timer on the main run loop that calls deliveredNotifications — a synchronous XPC round trip. A banner nobody clicks stays in Notification Center, so its timer never goes away. Sampled with nine outstanding: a fifth of the UI thread inside that XPC, every window juddering, one more timer per agent turn. Drive NSUserNotificationCenter directly with a delegate of our own: the click arrives through didActivateNotification, the pane rides in the identifier, and nothing runs on the main thread until the user clicks. notify-rust's show is no longer called on macOS, since it is that crate and would replace the delegate; only set_application stays, to name a bare binary. Claude-Session: https://claude.ai/code/session_01VuYUPiDEhQX6aQ4WQZbEGn |
||
|
|
644945d137 |
style(ui): flatten the inline controls and give tooltips a real shortcut slot (#803)
* style(theme): drop the lift under every inline control Theme::shadow gates exactly one thing -- the shadow_xs an inline control (button, input, select trigger, checkbox, radio, slider knob) paints under itself -- and never the drop shadow on a menu, tooltip or popover, which each draw theirs unconditionally. Left on, every field and button in the window carried a faint lift that nothing else here has: this chrome separates surfaces with low-contrast fills and hairlines, so a control sitting a millimetre above the panel was the one place claiming depth. Panels that really do float keep their shadow. Also bumps the gpui-component pin, and records why the switch and slider keep their accent: both were tried on the neutral ramp the segmented controls use, and a dark-grey "on" against a light-grey "off" turned out not to be a large enough step to read while scanning a column of rows. * style(tooltip): render a chrome tile's chord as a chord chord_hint pasted a label and its shortcut into one string -- "Hide sidebar <cmd>B" -- and handed that to Button::tooltip. Inside the card the chord then wore the label's own size and colour, so the tooltip read as one odd sentence rather than as a name with a shortcut beside it. Tooltip already has a key_binding slot that sets a chord apart on the right, a size down, in muted_foreground. What was missing was a way to hand Button a built tooltip instead of a string; gpui-component grew tooltip_element for that. chord_hint becomes chord_tooltip, and key_hint gains a key_stroke sibling so a caller can reach the Keystroke rather than only its formatted text. * style(settings): one field width, and a chevron that is not a patch The right-hand column had three widths, each picked where it was written: text fields 260, sliders 240, dropdowns 180. Every row still ended on the same right edge, so on one page the difference read as controls aligned carelessly rather than as controls of different kinds -- and moving between Appearance and Terminal, where the mix differs, the column visibly changed width. FIELD_W is the one number now, at 260, the widest of the three because it is the one with a requirement behind it: a font name or a shell path has to fit untruncated. The Program row's shell picker was a ghost button, which fills a rounded rectangle while its menu is open, sized by hit_target to the 24px accessibility floor -- exactly the field's inner height, so that fill met the border top and bottom and looked like a patch stuck over the field's right end. It now draws with no fill in any state, the way Select draws its own chevron. Its menu was min_w(200) anchored TopRight on a chevron that sits inside the field, so it hung off the field's right half with its left edge 110px in from the field's own; it is now as wide as the field it drops out of. |
||
|
|
d16746a9af |
fix(deps): restore the lockfile edges #799's merge walked back (#802)
The merge for #799 re-resolved Cargo.lock and pointed twenty consumers at older copies of dependencies that were already in the tree for other crates. No `version =` line moved, so the change is invisible to the usual scan of a lockfile diff, but the graph regressed: * 15 crates off `windows-sys 0.61.2` onto `0.60.2` (anstyle-query, anstyle-wincon, dirs-sys, errno, miow, muda, nu-ansi-term, quinn-udp, rustix, socket2, stacker, tempfile, tray-icon, uds_windows, winreg) * `winapi-util` off `windows-sys 0.61.2` all the way onto `0.48.0` * `gpu-allocator` off `windows 0.62.2` onto `0.58.0` * `iana-time-zone` off `windows-core 0.62.2` onto `0.58.0` * `dlib` off `libloading 0.8.9` onto `0.7.4` * `bindgen` off `itertools 0.13.0` onto `0.11.0` Nothing in that PR asked for it. Its only dependency change was the gpui fork rev, and the range it moved over touches one file in `crates/gpui/src/elements/list.rs` and no manifest, so the resolution was incidental to the merge rather than required by it. This points those twenty edges back at the versions they held before, which is what a fresh resolve picks. Every version already present in the lock stays present: `windows-sys 0.60.2` is still there for `notify 8.2.0`, which pins `^0.60.1`, and the older `windows`/`windows-core`/`libloading`/ `itertools` copies still serve their own consumers. So this drops no duplicate builds; it only stops the newer copies from being compiled alongside older ones for crates that had already moved on. Lockfile only. No manifest and no source changes, and `cargo metadata --locked` accepts the result without wanting to rewrite it. |
||
|
|
cb710c4d79 |
deps: bump async_zip from 0.0.18 to 0.0.19 (#764)
Also bump the version requirement in Cargo.toml, which dependabot left at 0.0.18 and which made every --locked job fail. |
||
|
|
081e191bb0 |
perf(diff-overlay): draw the patch as a virtualised row list (#799)
The overlay built its whole patch as a nested element tree on every frame: a card per file, a header per hunk, six elements per line. gpui notifies the view on each scroll wheel event, so a few hundred lines of diff rebuilt tens of thousands of elements tens of times a second, and the window stalled. Flatten the tree into one row per line in a new `diff_list` module and draw it with `gpui::list`, which builds only the rows on screen. The rows are rebuilt only when what they are built from changes, so scrolling no longer re-splits hunks or re-clones every line, and a change to one file splices just the rows it touched rather than resetting the list and losing the scroll position. The key that decides a rebuild takes the snapshot each frame was asked about even when it matched only by contents. A probe that finds nothing new still lands a fresh `Arc` over an equal snapshot; a key left pointing at the old one would go on walking the whole patch to prove the two equal, once per wheel event, which is the cost the key exists to avoid. A list counts a row it has not laid out yet as zero tall, which left the scrollbar reading an 800-line patch as one viewport: its thumb filled the track, and a drag from top to bottom travelled 248px and stopped. The rows below the fold are counted at the 19px both views already give a line of a patch, through `ListState::with_size_hint` — added to the gpui fork for this, `Cargo.lock` following its `tty7` branch to `ece710e3`. A card cannot survive that flattening — its rows are separate items now — so the frame it drew is gone, and with it the grey header bars and hunk bands that made the overlay the one view in the app still speaking gpui-component's default container language. The rows take the source control panel's own measurements instead: 26px, 10px inset, 5px radius, colour only under the pointer. The title bar's view switch loses its border for the same reason. |
||
|
|
6474e25a24 | deps: bump the cargo-minor-patch group across 1 directory with 3 updates (#776) | ||
|
|
20b73adb2a | chore(release): v26.9.1 | ||
|
|
37be703d5b | chore(release): v26.9.0 | ||
|
|
436e9c4320 |
deps: bump the cargo-minor-patch group with 2 updates (#724)
Bumps the cargo-minor-patch group with 2 updates: [uuid](https://github.com/uuid-rs/uuid) and [ureq](https://github.com/algesten/ureq). Updates `uuid` from 1.24.0 to 1.24.1 - [Release notes](https://github.com/uuid-rs/uuid/releases) - [Commits](https://github.com/uuid-rs/uuid/compare/v1.24.0...v1.24.1) Updates `ureq` from 3.3.0 to 3.4.0 - [Changelog](https://github.com/algesten/ureq/blob/main/CHANGELOG.md) - [Commits](https://github.com/algesten/ureq/compare/3.3.0...3.4.0) --- updated-dependencies: - dependency-name: uuid dependency-version: 1.24.1 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: cargo-minor-patch - dependency-name: ureq dependency-version: 3.4.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: cargo-minor-patch ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> |
||
|
|
46759b8a01 |
fix(input-bar): read column widths from unicode-width, not a hand-rolled table (#704)
* fix(input-bar): read column widths from unicode-width, not a hand-rolled table The input bar scored every character against a hand-written list of code-point ranges. Anything the list missed counted as one plain column, so `🀄`, `⌚` and every combining mark pulled the rest of the row a column left, and clicks, wrapping and the caret all landed off by that much (#701). The grid gets its widths from `unicode-width` by way of `alacritty_terminal`, so read the same table. Zero-width characters then need a cell to ride in: group each base with the marks that follow it, so the shaper sees one run and composes `é` instead of setting `e` and its accent side by side. An emoji presentation sequence is re-scored as a string the way the grid re-scores it, so `❤️` is two columns in the bar as well. A ZWJ sequence stays two cells on purpose — that is what the grid makes of it, and composing it here would put the bar a column off from where the text lands. * fix(input-bar): derive click and wrap geometry from the cells the bar draws `input_cells` re-scores an emoji presentation sequence to two columns and hands a stranded combining mark a column of its own, but `input_char_positions` kept walking the text character by character — so `❤️` was drawn two columns wide and counted as one. Everything geometric read the short count: a click on `X` in `❤️X` selected past it, wrapping broke a column early, and vertical caret motion aimed at the wrong column. Walk the same cells instead. Only the base of a cell carries the width, so a click still lands on the base rather than a mark riding on it, and the riders sit at the column the caret takes after the cell. A cell now also tints as a unit when a selection covers any character in it — it is one glyph, so half-highlighting it drew a mark unselected next to its selected base. |
||
|
|
2cdc26f357 |
Wire hooks, resume and detection for Kimi Code CLI (#694)
* feat(agents): wire hooks, resume and detection for Kimi Code Kimi Code CLI takes its hooks as [[hooks]] entries in the same config.toml that holds the user's providers and models, so this adds a third install strategy — a format-preserving TOML merge on toml_edit — beside the JSON map merge and the owned files. Like Qwen it reports permission requests first-class, so it gets no Notification hook. Resume rides `kimi --session <id>`; fork stays unwired, Kimi documents none. Closes #693 Signed-off-by: Austin Spraggins <spragginsdesigns@gmail.com> * fix(agents): harden the Kimi Code TOML hook merge and its resume flags The TOML merge strategy the Kimi wiring introduces round-trips a shared config.toml cleanly, but three gaps sat behind it. `hooks_state` counted only the marked entries that still named an event, so a hand-edit that dropped the key off one of nine entries left the remaining eight matching the roster exactly and the file reported Installed with a broken entry in it. Every marked entry now counts, which is what the JSON merge already did and what `refresh_hooks` needs to see. A `hooks = []` spelled as an empty inline array made install fail outright -- toml_edit keeps an empty array and an array of tables apart, but the two say the same thing and neither carries any configuration. It is now promoted rather than refused. Every other wrong-shaped `hooks` key -- a string, a table, a non-empty inline array -- still refuses with the file left byte-for-byte alone. `Stop` is not the only way a Kimi turn ends: its own event reference says `Stop` does not fire on interrupts and `Interrupt` fires instead, and a turn that dies on an error reports `StopFailure`. Without those two an Esc or a failed turn left the pane on "working" for good and `tty7 wait` could only ever time out. Both are observation-only events and report the same end of turn `Stop` does. On resume, `--agent` and `--agent-file` join the stale flags: Kimi rejects either next to `--session` at startup, and resuming rebinds the session agent by itself, so replaying them turned a working resume into a launch error. Tests cover the wrong-shaped `hooks` keys, a config.toml that does not parse on both install and uninstall, a file that does not exist yet, a second install being byte-for-byte the first, mangled and surplus marked entries, an uninstall threading between the user's own entries and the tables after them, and the `--session=<id>`, bare `--session`, `--continue` and `--agent` spellings on the resume path. --------- Signed-off-by: Austin Spraggins <spragginsdesigns@gmail.com> Co-authored-by: l0ng-ai <24760907+l0ng-ai@users.noreply.github.com> |
||
|
|
9f34cd3501 |
fix(editor): stop scrolled-out text painting over the line numbers
Bump the gpui-component fork to 070d1a2, which clips the editor's scrolling content to the right of the gutter. Text, selections, indent guides and the cursor all paint from a bounds origin that horizontal scrolling has already shifted left, so scrolled-out content kept painting under the line-number column; the only thing hiding it was the gutter quad painted afterwards, which works only while `editor.gutter.background` is opaque. `apply_theme` clears that key to transparent so the panel can sit on a gradient or image window background without a seam, which is exactly the case the upstream code does not cover. Note that dependency in the theme, so the next person to touch it knows the transparent gutter is not free. |
||
|
|
0df604054d |
fix(daemon): keep a lingering daemon findable and reapable after quit-and-stop (#655)
* fix(daemon): keep a lingering daemon findable and reapable after quit-and-stop Quit-and-stop could strand a daemon that had already unlinked daemon.sock and deleted daemon.pid but never finished exiting: libc exit() runs atexit handlers and static destructors beside dozens of live threads, and a finalizer that blocks leaves the process holding the singleton lock with no name on disk. Every later launch then spawns a daemon that stands down against the lock and times out red, forever. Three changes, each a fallback for the others: - on_shutdown keeps the pidfile: once the endpoint is unlinked it is the only handle anything has on a process that is not gone yet. A pidfile that outlives a clean exit was already handled by recorded_daemon_is_dead and the reap path. - The daemon exits through _exit(2) (after flushing the logger), skipping the atexit/destructor window entirely; everything owed to disk is flushed explicitly in on_shutdown. - spawn::stop reaps with the pid it captured before asking the daemon to die, instead of re-reading a pidfile an old build's shutdown may have wiped mid-stop; reap_recorded_daemon keeps the pidfile when the process survives even SIGKILL, so the next attempt still has someone to reap. * review: fix stale stop() comment, pin the mid-stop pidfile-vanish ordering in the test The comment at the top of stop() still claimed a clean shutdown removes the pidfile, which this branch just made untrue; it now states the real reasons the pid is captured early. The vanishing-pidfile test now asserts the sweeper's delete actually landed while stop() was waiting, so a future shrink of PROCESS_EXIT_TIMEOUT cannot silently turn it into a weaker scenario. --------- Co-authored-by: l0ng-ai <24760907+l0ng-ai@users.noreply.github.com> |
||
|
|
05de7ae33a |
fix(new-tab): keep the SSH menu inside a menu's shape (#649)
* fix(new-tab): keep the SSH menu inside a menu's shape The saved-host rows carried names and endpoints long enough to drag the panel out to the 500px ceiling PopupMenu falls back to, and the row that meant to elide was clipped mid glyph instead. The menu now stops at 360px, and a row that runs out of room cuts the endpoint first — the name is what the reader is picking by, so it keeps whatever is left rather than being squeezed to "..". The height ceiling moves up to fit the shape everyone actually sees — nine shells, both headings, six hosts and the two closing rows — so the default menu arrives whole instead of scrolled with "Local" cut off above, and is capped again against the window so a short one never gets a menu taller than itself. The rule above the split hint goes: a separator divides two lists of things to pick, and the hint is a footnote about the list it follows. Bumps gpui-component, where a scrollable PopupMenu painted a scrollbar whether or not it overflowed, custom rows could not elide, and labels had no padding of their own. * fix(new-tab): measure the menu ceiling off the viewport, and elide nameless hosts `window_bounds()` answers how a window should be reopened after it is closed, so a fullscreen macOS window reports the bounds it would restore to rather than the screen it currently fills. A terminal spends much of its life fullscreen, where that reading capped the menu at 80% of a window nobody is looking at — putting back the scrollbar and the cut-off `Local` this branch is here to remove. `viewport_size()` is what every other window-relative size in the app already measures against. A host saved on its address alone is *named* `user@host:port` and carries no note, so it took the plain-item path — bare text with nothing to elide against, on the longest string in the menu and the row least able to cut it. Every host row is a custom element now, and `menu_row` drops its right half when the note is empty rather than holding the gap open with a zero-width child. Also drops 17 unrelated dependency downgrades that rode along with the `gpui-component` bump. The lockfile moves only the three `source` lines it meant to; `cargo check --locked` accepts it. --------- Co-authored-by: l0ng-ai <24760907+l0ng-ai@users.noreply.github.com> |
||
|
|
f1144deb9f |
fix(ui): restyle the in-app notification to sit in tty7's own visual language (#646)
Bumps the gpui-component pin to pick up the notification restyle: flow-positioned status icon and close button that centre on the first line at any wrap count, a hairline-shadow surface in light theme, and a type ranking expressed in rems so it survives the ui_font_size setting. |
||
|
|
71c6783fb4 | chore(release): v26.8.3 | ||
|
|
2dc6a88af6 |
merge: main into the Source Control branch
Conflicts were the two streams touching the same seams, resolved by taking the newer decision on each side: - main's interface font scale (rems tokens) wins in right_panel.rs; the SCM panel keeps its local px steps until it moves onto that scale, and the now-unused PANEL_TEXT constants are gone. - main's l10n_keys! macro (idents only) means the key list carries no doc comments any more; our SCM keys fold into it, and PanelUntracked stays deleted — its only caller was the panel this branch replaced. - main's Command::localized palette style carries our Git group; ORDER keeps main's visibility and our width. - main's ansi_seed/clear_ink refactor in presets.rs carries the lane colours: lanes() now clears through the same helper semantics uses. - file_tree keeps both: main's drag-and-drop targets and this branch's git decorations per row. - diff_overlay keeps both: main's sidebar-count write-back on snapshot install and this branch's epoch read and untracked preview. - main's window.prompt SSH-close confirmation supersedes the bespoke modal our branch still carried; main's tile-glyph revert stands. - main's two new guards are satisfied: the fourteen SCM actions carry authored names on the Keybindings page (their palette wording, plus a new CmdGitToggleGraph), ja translates ScmDetached, and CmdGroupGit joins the kept-in-English list — Git is a name. 2571 tests, 0 failures. |
||
|
|
44f0683d0a |
fix(sidebar): cut labels on grapheme clusters, not on chars (#450)
The sidebar's elision measures against real glyph widths but slices by
`char`, so it can satisfy every width check and still hand back a torn
cluster. Scanning budgets from 30px to 200px over emoji fixtures, 48
widths produced output no font can render as intended:
"release-…\u{200d}👩\u{200d}👧" a joiner with nothing in front of it
"lon…\u{fe0f}" the variation selector lands on the ellipsis
"abcdef…🇳ghijklmnopqr" half a flag, which renders as a bare N
A tab title carrying an emoji is not exotic — plenty of TUIs and coding
agents put one there — and the second case is the same U+FE0F this repo
already carries an alacritty patch for.
`elide_keep_edges`, the tail-only fallback, and `short_title`'s 40-glyph
clamp now index grapheme clusters. `elide_path_keep_tail` cuts on `/`
and was already safe. Widths are unchanged: clusters are measured the
same way chars were, so every existing elision test still passes on the
same fixtures.
`unicode-segmentation` is already in the tree via gpui; pinning it here
adds one line to Cargo.lock and no new code.
Tests assert the property rather than the symptom: whatever survives on
either side of the ellipsis has to be a cluster-aligned prefix and
suffix of the input. That catches any tear, not just the three shapes
found here. Written first, confirmed failing on all three cut sites, and
green after.
|
||
|
|
0106430ecd |
merge: main into the Source Control branch
The one conflict is an import list in `diff_overlay.rs`: this branch added `SharedString` for the unified view's row labels, main added `Background` and `Hsla` for the window backdrop work. Both sides are still used, so the resolution is the union. Worth recording why this merge happened when it did. `main` moving is not normally urgent — branch protection dropped its strict check, so a branch behind main still merges — but a *conflicting* branch is different: GitHub cannot compute `refs/pull/N/merge`, and every workflow that triggers on `pull_request` silently stops running. Three pushes in a row registered no CI at all on #424 while other PRs kept going green, which reads as a GitHub incident and is really just an unresolved conflict. |
||
|
|
61efe27f2d |
feat(windows): add native backdrop material presets (Mica / Acrylic /… (#412)
* feat(windows): add native backdrop material presets (Mica / Acrylic / Blur) Adds a Background material dropdown (Auto / Blur / Mica / Mica Alt / Acrylic / Off) that maps onto the native Windows backdrop APIs already provided by the gpui fork — Mica and Mica Alt via DwmSetWindowAttribute(DWMWA_SYSTEMBACKDROP_TYPE), Acrylic via the new DWMSBT_TRANSIENTWINDOW material, and Blur via the classic ACCENT_ENABLE_ACRYLICBLURBEHIND path — with no fork changes required. * config: introduce WindowBackdrop in tty7-core with lenient kebab-case deserialization, defaulting to Auto for existing configs * theme: resolve the backdrop through a build-number fallback chain (Mica/Mica Alt need Windows 11 22H2, Acrylic needs 22H2 natively and 1809 via classic acrylic, Blur needs 1809; older builds fall back to plain translucency) and default the background alpha to SYSTEM_MATERIAL_OPACITY (0.82) while a material is active * settings: replace the blur toggle with a localized backdrop dropdown that only lists the presets the current Windows build actually supports, and keep the settings panel fully opaque so workspace translucency never shows through it * theme: make the file sidebar and right detail panel follow the window opacity so the backdrop material shows through the whole workspace, keeping row-level accents opaque for readability * i18n: add backdrop keys for en, zh-CN and ja-JP, covered by the translation completeness test * feat(theme): let the sidebar and right panel follow the window opacity * update GPUI * fix(windows): gate the sidebar translucency to translucent windows and sync the opacity slider fix(windows): gate the sidebar translucency compensation to active materials * fix(windows): derive the material opacity default from the resolved appearance * fix(theme): keep WindowBackdrop semantics consistent on non-Windows f * fix(theme): stop Windows-only materials from pinning the blur on other platforms * docs(changelog): document the Windows backdrop material settings * refactor(theme): share the default window-opacity derivation * fix(ui): keep gradient presets behind the settings panel and scope its fallbacks * fix(ui): keep the settings theme picker legible and the backdrop label honest f * fix(theme): let every backdrop variant defer to the local blur toggle on non-Windows * fix(settings): restore the backdrop dropdown selection on locale refresh * fix(ui): keep the opened-file editor surface opaque under window translucency * fix(settings): rebuild backdrop options after selection * fix(settings): ignore synced windows backdrop overrides on other platforms * fix(settings): preserve synced windows backdrop on non-windows reset * fix(diff): keep the full-window overlay background opaque * fix(windows): keep Auto opaque and stop the backdrop from misreporting itself Ten findings from a review of the backdrop-material work, all in the Windows-only paths. The root one: `material_active` treated `Auto` as a material whenever the legacy blur toggle happened to be on. `Auto` is the default in every config written before this setting existed, and plenty of them carry `window_blur: true` from the switch that no longer renders on Windows, so an untouched install would drop from opaque to 0.82 alpha - with its file sidebar and right panel at 0.15 - on first launch after the update, with no visible control to undo it. Only an explicit pick in the dropdown now buys the translucent defaults. The switch comes back on Windows while the backdrop is `Auto`, since that is exactly when the legacy flag still decides something. The rest: - Mica and Mica Alt fell back to `Blurred` with no lower bound, asking for a blur that does not exist below 1809 - and build 0, which is what a failed `RtlGetVersion` reports. They now degrade to plain translucency like `Blur` and `Acrylic` already did. - Acrylic is no longer offered below 22H2, where it resolves to the very same classic WCA blur as `Blur`. A test now asserts that no two offered presets render identically on any build. - `reload_from_config` re-applied the theme and the opacity slider but not the backdrop dropdown, so an external config change switched the window's material while the control kept naming the old one. - The settings, opened-file and diff overlays were made opaque so the OS backdrop cannot show through their text; that also hid the theme background image, which used to show through them. They paint their own copy of it now, and the fill they share moved into `theme::overlay_background`. - The SFTP transfers tray painted `workspace_surface_color` inside the right panel, which already paints it, stacking the same translucent surface twice into a darker band with a hard seam. - `apply_theme` re-issued `set_background_appearance` on every `Config` mutation in every window. With a DWM material that now costs a `SetWindowPos(SWP_FRAMECHANGED)` frame recalc, so dragging the opacity slider recalculated the frame once per mouse sample; it is skipped when the appearance is unchanged. * fix(ui): dim the overlay background image, and stop telling Windows it is macOS Two defects found while driving the previous commit's changes in the app. The overlays repaint the theme background image over their own opaque fill, so it survives them being made opaque - but nothing dimmed it. Before those overlays were opaque the image reached the eye through their translucent fill; painting it at full strength put the settings text straight on top of the wallpaper and made the panel unreadable at any image opacity above about half. They now paint the image and then the workspace's own fill over it, which is exactly the strength the image had through these overlays before, and which needs no new constant to say so. Shared as `app::overlay_surface_layers`, empty when the theme has no image so a themeless window paints no second pass of anything. The Windows-only blur row reused `SettingsBlurDesc`, whose text ends in "(macOS)". It gets its own key in all three locales, describing the job the flag actually still has on Windows: feeding the `Auto` material. --------- Co-authored-by: l0ng-ai <24760907+l0ng-ai@users.noreply.github.com> |
||
|
|
b4e7add65d | chore(release): v26.8.2 | ||
|
|
9914a939b4 |
chore(deps): regenerate the lock for tty7-core's smallvec
The dependency was declared but the lock was never refreshed, so every worktree building against it produced the same one-line diff. |
||
|
|
51c35aac1f |
fix(terminal): resize ConPTY panes with conhost's semantics, in stream order (#415)
ConPTY emits no repaint after a resize; conhost silently re-anchors its layout and keeps painting with absolute cursor addresses computed against it. Measured live: growing the window keeps rows and cursor pinned and opens blank rows below, and shrinking scrolls the last written row to the new bottom. The grid resized the alacritty way instead, so after a maximize every absolute-CUP paint landed mid-screen inside the old output. The vendored alacritty_terminal now has a conpty_resize mode mirroring conhost's model (fork rev 1276f12); every Windows pane opts in. Separately, a resize during a burst of output reflowed ahead of the backlog (up to the gate's 16 MiB of old-width bytes). The daemon now echoes a Size frame to the controller at the exact stream position where the PTY geometry changes, and a client that probes the new resize-echo feature defers its reflow to that marker. Remote routes and older daemons keep the reflow-at-request-time path. |
||
|
|
bb72be338d |
fix(windows): respect system proxy for remote server downloads (#364)
The GUI update check already uses reqwest, which reads the Windows system proxy from the registry by default. The remote server installer / bundled-server fallback uses ureq, which only reads HTTP_PROXY/HTTPS_PROXY environment variables unless the win-system-proxy feature is enabled. Enable ureqs win-system-proxy feature so that release downloads inside the daemon also honor the Windows system proxy set by tools like Clash (System Proxy mode), v2rayN, etc. This is a no-op on non-Windows platforms. Fixes the inconsistency where the update check could reach GitHub through the proxy but the actual download would time out trying to connect directly. |
||
|
|
4a8a4bcbaa |
feat(proxy): macOS system proxy, Windows SOCKS parsing, manual override (#367)
Resolve an HTTP/SOCKS proxy for tty7's own update checks and release downloads, from (in order) a new `http_proxy` config field, the platform system proxy — Windows registry / macOS SCDynamicStore — and the HTTP_PROXY/HTTPS_PROXY/ALL_PROXY environment variables. Programs running in a pane are deliberately unaffected: they inherit their proxy from their own environment, as in any other terminal. Fixes #365. |
||
|
|
a7de7db2c4 |
feat(windows,macos): clickable toasts, richer context, and i18n (#373)
Desktop notifications now carry the pane they came from: clicking one reveals that pane's window, tab and split. Windows shows a WinRT toast with an `Activated` handler, macOS uses mac-notification-sys' click response, and both route through the existing tray dispatch channel. Linux keeps the plain notify-rust path. Titles gained context — an agent name or the machine, then the workspace — and bodies name the command or agent alongside the duration, all of it translated. Notification text is sanitized on every path: it comes off the terminal, and a stray control byte used to make the Windows toast XML fail to parse and lose the notification outright. Co-authored-by: Hongwei Qin <exqinhongwei@outlook.com> |
||
|
|
e1531cdea6 |
revert(windows): drop the taskbar status dot (#377)
The per-window taskbar overlay badge (#355, for #199) is removed, and with it the in-flight follow-up that was making its green "finished a turn" state reachable: the feature is not wanted. Nothing shipped — the badge only ever existed in Unreleased — so this is a plain removal rather than a deprecation, and its CHANGELOG entry goes with it instead of gaining a "Removed" counterpart. What goes: `ui::taskbar` and its `ITaskbarList3::SetOverlayIcon` poll, the `taskbar_status_icon` config flag and its Settings → Window & Tabs row and strings, `Tty7App::taskbar_signals`, `TerminalView::shell_busy` / `RemoteTerminal::shell_busy` (the overlay was their only caller), the `raw-window-handle` dependency and the `Win32_UI_WindowsAndMessaging` feature it needed, and the feature docs in both languages. A stale `taskbar_status_icon` left in someone's `config.json` is ignored, as any unknown key is. The tray badge and the in-window status dots are untouched; they were always the ones the taskbar was mirroring. |
||
|
|
27bb1864df |
feat(windows): taskbar status overlay per window (#355)
* feat(windows): taskbar status overlay per window (#199) Stamp a colored status dot on each window's taskbar button using the same palette as the in-window agent dots: - blue while a shell command or agent is working, - amber when an agent is waiting on the user, - green when work finishes while the window is unfocused (cleared on activation). Adds a `taskbar_status_icon` setting (default on, Windows only) and a Settings -> Window & Tabs row. The overlay is updated by a foreground poll that aggregates agent status and shell busy state across each window's panes, diffing against the current taskbar badge and only calling ITaskbarList3::SetOverlayIcon when the badge changes. Includes unit tests for overlay priority and the done-while-unfocused edge tracking. * fix(taskbar): retry a failed overlay instead of caching it as drawn Four fixes on top of the overlay: - A failed SetOverlayIcon was still recorded in `shown`, so a badge the taskbar never took was remembered as drawn and never retried. Stamp now reports success, and a failure drops the interface so the next tick re-creates it — which is also what an Explorer restart needs. - `create_failed` was a permanent latch: one CoCreateInstance failure killed the badge for the whole process, though Explorer may simply not be up yet when the first window opens. Use the tray's attempts/cooldown backoff instead, which this module otherwise copies. - The overlay's accessibility description was hard-coded English in an app that localizes everything else. Reuse the panel and tray strings. - Render the dot at 32px, not 16. SetOverlayIcon wants 16x16 at 96 dpi, so at 150%/200% scaling the shell upscaled a 16px icon; `tray::icon` already renders at 32 off macOS for the same reason. Also drops the Win32_Graphics_Gdi feature: CreateIcon, DestroyIcon and HICON all live in Win32_UI_WindowsAndMessaging, and the build and the taskbar tests pass without it. Claude-Session: https://claude.ai/code/session_01H9QqEZ6JH3dGS6atEcf6ab --------- Co-authored-by: l0ng-ai <24760907+l0ng-ai@users.noreply.github.com> Co-authored-by: l0ng-ai <ysdpk123@gmail.com> |
||
|
|
603bca171e |
feat(updater): add windows updates and cross-platform nightly support (#330)
* feat(updater): add windows online updates
* feat(updater): support online updates for windows portable zip builds
f
* feat(updater): support online updates for nightly build
* fix(updater): strengthen post-download update verification
* feat(updater): support explicit stable and nightly channel switching
* fix(i18n): localize update settings ui
* fix(settings): prevent slider value labels from wrapping
* feat(updater): drop the nightly channel, refuse all-users Windows installs
Follow-up to the Windows updater work on this branch, applying maintainer
review.
Nightly is a build channel, not an update channel. The updater consults
`/releases/latest` again and nothing else, so it behaves on Windows exactly
as it already does on macOS: a Nightly build is offered the stable release
that supersedes it and graduates out of the prerelease, and no rolling
prerelease can become a source of code that gets executed on a user's
machine. Removed with it: the `UpdateChannel` enum and its version-string
inference, the `tags/nightly` query, the cross-channel version-ordering
bypass, the Settings → About channel row, the rolling-tag
`update-manifest.json` and the i18n keys that only served them.
`parse_version` and `is_update_available` are byte-identical to main again.
Nightly builds are untouched, and still carry tty7-updater plus the macOS
update archive — a Nightly user needs a working helper to reach the stable
release that replaces their build.
An all-users Windows installation is no longer updated in place. Running the
release Setup silently as the signed-in user cannot replace
`C:\Program Files\tty7`: Inno resolves `{autopf}` to `%LocalAppData%\Programs`
and installs a second copy beside the real one, or re-launches itself
elevated and puts a bare UAC prompt for an unsigned executable in `%TEMP%` in
front of a user whose GUI just vanished. tty7 declines both and points at the
release page. Detection reads Inno's own `HKLM` state for the frozen AppId and
independently probes whether the directory accepts writes, so a relocated or
pruned installation is caught too; the decision is a pure function with unit
tests, and it is re-checked before the download as well as during it.
Release and Nightly now verify the Windows packages they just built, mirroring
the macOS update-archive step: the install marker, tty7-updater.exe, the ZIP
layout the updater will accept and the PE versions it will demand. Every fact
the updater checks on the user's machine after downloading is checked here
instead, so a packaging mistake fails the build.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: l0ng-ai <24760907+l0ng-ai@users.noreply.github.com>
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
||
|
|
618855cf4a |
fix(windows): brand toast notifications with a tty7 AUMID (#340)
* fix(windows): brand toast notifications with a tty7 AUMID (#339) * fix(windows): only write the toast shortcut where it is ours to write The AUMID shortcut was rewritten on every launch, which broke two cases the review caught on a real machine. An elevated install owns `%ProgramData%\...\tty7.lnk`, so writing a per-user copy listed "tty7" twice in the Start Menu and left an orphan pointing at a deleted exe once the uninstaller had removed only its own. And `cargo run` repointed the installed shortcut at `target\debug`, permanently, for anyone who both installs tty7 and builds it. So decide before writing. An all-users shortcut settles the question by itself — branded if the installer stamped our AUMID on it, otherwise we stay on the PowerShell identity, because the alternative is littering a Start Menu we cannot clean up. Otherwise we refresh the single per-user `tty7.lnk` Inno's default install owns anyway, and only when it is not already ours, and never from a cargo build directory. A dev build still brands the process for taskbar grouping, and still gets branded toasts when an install left a stamped shortcut behind — Windows asks that the AUMID be registered, not that it point at the process using it. Reading a shortcut back needs `IShellLinkW::GetPath`, hence the `Win32_Storage_FileSystem` feature; `SLGP_RAWPATH` keeps it from chasing a moved target over the network. Also close the window this opened. The shell indexes a new `.lnk` asynchronously and, for an AUMID it has not seen, `Toast::show()` reports success and drops the toast — measured, it does not return an error. A shortcut we wrote seconds ago is therefore not yet proof of anything, so toasts keep the PowerShell identity for half a minute after we write one: ugly beats invisible. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: l0ng-ai <24760907+l0ng-ai@users.noreply.github.com> Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
a6754b28bc | feat(update): install verified macOS releases in app | ||
|
|
984452a83b |
fix(deps): pick up the gpui fix for the portal-triggered borrow panic on Linux
26.8.1 panics on launch under Wayland on a VMware Ubuntu guest:
gpui_linux/src/linux/wayland/client.rs:924: RefCell already borrowed
The xdg-desktop-portal event source notified windows of the initial
color-scheme and button-layout replies while still holding
`client.borrow_mut()`, and those callbacks re-enter GPUI, which reaches
the same `RefCell` through `with_common`. Whether it fires depends on
whether the portal reply beats window creation, so a slow VM loses that
race every time.
Fixed in the fork (l0ng-ai/zed@3a4acfd) for both the Wayland and X11
clients by collecting the window pointers and dropping the borrow before
notifying. Windows and macOS never compile that crate.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
||
|
|
00607cc52f | fix(release): bump tty7-cli/tty7-core/tty7-server lockfile versions to 26.8.1 | ||
|
|
6fc8bcb2cf | chore(release): v26.8.1 | ||
|
|
74f4f1a35a | chore(release): v26.8.0 | ||
|
|
b8dc38fb98 |
fix(input): anchor the IME candidate window at the fake caret, not the parked cursor (#275)
Cursor-hiding TUIs (Kimi CLI, Ink apps) draw their caret as a reverse-video cell and leave the real cursor wherever the frame's last write ended — for Kimi that is the input box's right border, and the IME candidate list was stranded there. When the cursor is hidden and its row holds exactly one caret-sized inverse run, snap the IME anchor (and the marked-text preview) to that run; rendering is untouched. The gpui side (bumped here) now also answers IMR_QUERYCHARPOSITION — the query the Windows 11 Microsoft Pinyin IME uses instead of CANDIDATEFORM — and re-anchors the candidate window on every WM_IME_COMPOSITION. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
86eba1e2c2 |
feat(cli): make a captured pane readable, and stop panicking on a closed pipe
The CLI's own --help calls it "built for coding agents", but `capture` handed back the daemon's raw PTY bytes, which is the least readable thing it emits, and every verb panicked when its reader hung up. `capture --plain` replays those bytes through a terminal grid instead of stripping escapes from them, using the same alacritty_terminal rev the GUI renders panes with. The difference is not cosmetic: only the grid knows that a break at the pane's width was a wrap rather than a newline, that a CR meant "overwrite this line" rather than "end it", and which cell a wide char shares with its spacer. A regex gets the easy 90% and then invents the rest — on one real pane it turned 1193 lines into 2806. The size each segment needs comes for free: the daemon already sends DaemonMsg::Size right before every Snapshot, and the CLI was discarding it. Panes here measure 249 and 86 columns, so the hardcoded 120 would have wrapped both in the wrong places. Observing still resizes nothing. The pipe fix is two mechanisms with one contract. On Unix SIGPIPE goes back to its default disposition, which covers every write site at once and ends the process the way it ends `cat` (141). Windows has no such signal, so stdio::out recognizes the hung-up write and leaves quietly. Before this, 16 of 19 verbs printed a panic and a backtrace note for `tty7 ls | head -1`; `run` instead reported it as a failure with exit 1. Also adds skills/tty7, the Claude skill for driving this CLI. It shipped with a Python ANSI stripper, which is what prompted --plain; the script is gone. alacritty_terminal moves to [workspace.dependencies] so the GUI and the CLI cannot drift onto two revs of the fork. |
||
|
|
c275960ceb |
feat(cli): ship the CLI in every installer and put it on PATH at launch
The `tty7` CLI was built by every release run and thrown away: all four bundle scripts copied only `tty7-app`, and the upload glob covers `dist/`, which the CLI never reached. Nothing put it on PATH either, so the agent-facing half of the product was unreachable from a shipped install. Bundle it on all four platforms, and have the GUI link it up itself rather than hiding the step behind a menu item most people never find. The install has two halves. The environment half prepends the CLI's directory to this process's PATH before the daemon is spawned, so every pane inherits it — that alone makes `tty7` work where agents actually run, writes nothing to disk, and behaves the same everywhere. The on-disk half symlinks into a directory already on PATH (Unix) or appends to HKCU\Environment (Windows), and is allowed to fail. Candidate directories are a fixed list intersected with PATH, not the first writable entry on it: pyenv/rbenv/asdf/mise shim directories sit at the front of PATH on many machines and are writable, and anything dropped there is deleted on the next rehash — silently, days later. Debug builds get the environment half only. `target/debug` holds a `tty7` too, so otherwise a `cargo run` would repoint the developer's real `tty7` at a debug binary, and each isolated dev-verify instance would rewrite the PATH of the machine it is meant to stay away from. |