From 6f7d3a9aeba31111c00c6acbc67ec5a322005b49 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=9B=B7=E7=94=B5=E8=8A=BD=E8=A1=A3?= Date: Thu, 15 Oct 2026 18:14:25 -0400 Subject: [PATCH] T26: on Windows every title bar control takes its press: Ely cdc5ce8 occludes the drag region under its window buttons, title bar slots and column header items, where gpui answered the hit test as the caption; the Git and Files switch moves below the title bar, clear of the caption buttons; version 0.1.1 --- AGENTS.md | 5 +++-- Cargo.lock | 4 ++-- Cargo.toml | 4 ++-- PRD.md | 3 ++- TASKS.md | 4 ++++ src/workspace.rs | 14 +++----------- tasks/acceptance.md | 3 ++- 7 files changed, 18 insertions(+), 19 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index d10028c..ea84ffb 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -68,9 +68,9 @@ ELYTerminal. A terminal on GPUI and Ely components. `PRD.md` says what it does; - 2026-10-14: a key's release goes out only when the terminal saw its press, so a key a binding takes sends no release, as in kitty. - 2026-10-14: find opens on Cmd-F (Ctrl-Shift-F off macOS) beside the grid, outside the `Terminal` key context, so its keys never reach the program. Matches wash in Ely's `warning` color, the current one stronger; the newest is current first, Enter steps older and Shift-Enter newer. Shift-Enter binds under `TerminalFind > ElyInput`, the context of Ely's text input, whose own Shift-Enter binding takes the key. Find reads the grid as it shows: combining marks ride their cells, hidden cells are blank, wrapped rows join, words are Unicode; alacritty's own search reads no marks and knows only ASCII word edges. New output, a resize and a settings change search again; the current match stays the same occurrence: its line kept, grown or cut at its end, held under the same line above or in its place, before such a line elsewhere, nearest where scrolling moved it; else the nearest by line, then column. Searching and following take one pass over the text and the matches. - 2026-10-14: the program hears focus in and out under mode 1004 when the terminal's focus or the window's activation changes. The window wears the program's title, the app's name while it names none. A bell is logged and makes no sound. A full reset, `ESC c`, empties alacritty's title and sends only `CursorBlinkingChange`, in the parser's order, synchronized updates too; on that event the view replays the title through `set_options`, which sends it. -- 2026-10-14: the title bar is transparent, as Ghostty's: the content runs under it, Ely's column headers drag the window, and the terminal's header shows the program's title. macOS draws its traffic lights at Ely's origin; elsewhere Ely draws the controls, so windows ask for client decorations, which only Linux reads, and Ely's `ResizeBorder` gives the edges. Cmd-B and Cmd-Alt-B (Ctrl-Shift-B and Ctrl-Shift-Alt-B off macOS) toggle the sessions and right columns, kept in the settings. A button at each end of the title bar does the same: `PanelLeft` leads the first column, right after the traffic lights, and `PanelRight` ends the last, so neither moves when a column hides; its tooltip says Hide or Show. +- 2026-10-14: the title bar is transparent, as Ghostty's: the content runs under it, Ely's column headers drag the window, and the terminal's header shows the program's title. macOS draws its traffic lights at Ely's origin; elsewhere Ely draws the controls, so windows ask for client decorations, which only Linux reads, and Ely's `ResizeBorder` gives the edges. Cmd-B and Cmd-Alt-B (Ctrl-Shift-B and Ctrl-Shift-Alt-B off macOS) toggle the sessions and right columns, kept in the settings. A button at each end of the title bar does the same: `PanelLeft` leads the first column, right after the traffic lights, and `PanelRight` ends the last, before the window buttons Ely draws on Windows and Linux, so neither moves when a column hides; its tooltip says Hide or Show. The Git and Files switch tops the right column, below the title bar: on Windows the caption buttons left the last header no room for it. - 2026-10-14: session keys are Ghostty's: Cmd-T, Cmd-W, Cmd-1 to Cmd-8, Cmd-9 for the last, Ctrl-Tab and Ctrl-Shift-Tab; off macOS Ctrl-Shift-T, Ctrl-Shift-W and Alt with the digits. A digit past the last session goes on to the program. A new session runs the Shell setting in home or in the shown session's folder, as Start in says. A session whose program ends closes its row; the last row closes the window, and with no window left the app quits. A bell in a hidden session marks its row until it is shown. A hidden terminal hears no mouse release, so hiding it ends its mouse gesture as releases would: the selection drag ends, and the program hears each held button come up. A row's title is the program's, or the app's name while it names none. -- 2026-10-14: Files shows the shown session's folder in Ely's `Tree`, not `FileTree`, which takes every path up front and drops empty folders. A folder is read off the main thread when it first opens; the root and the open folders are read again whenever the session reports a folder, so at each prompt. Git's marks come from `git status --porcelain=v2 -z -- .`, run with `--no-optional-locks`, so it never holds `index.lock` against the user's own git, and under the user's `status.showUntrackedFiles`, so a home folder kept in git stays quick. An untracked folder marks what it holds. On macOS, whose file systems ignore Unicode forms, a name keeps its form on disk and meets Git's mark in composed form, as git's `core.precomposeunicode` writes it. A label built from a path shows each control character as its picture, `␊` for a newline: gpui shapes no newline, and a file may be named with one. A name not in UTF-8 keeps its bytes: its label shows it lossily, and its tree key adds the bytes after a NUL, which no path holds, so no two names share a key. A program without shell integration gets a line in the right column until it reports a folder. A control in a column header keeps its presses: Ely's header zooms the window on any double press. +- 2026-10-14: Files shows the shown session's folder in Ely's `Tree`, not `FileTree`, which takes every path up front and drops empty folders. A folder is read off the main thread when it first opens; the root and the open folders are read again whenever the session reports a folder, so at each prompt. Git's marks come from `git status --porcelain=v2 -z -- .`, run with `--no-optional-locks`, so it never holds `index.lock` against the user's own git, and under the user's `status.showUntrackedFiles`, so a home folder kept in git stays quick. An untracked folder marks what it holds. On macOS, whose file systems ignore Unicode forms, a name keeps its form on disk and meets Git's mark in composed form, as git's `core.precomposeunicode` writes it. A label built from a path shows each control character as its picture, `␊` for a newline: gpui shapes no newline, and a file may be named with one. A name not in UTF-8 keeps its bytes: its label shows it lossily, and its tree key adds the bytes after a NUL, which no path holds, so no two names share a key. A program without shell integration gets a line in the right column until it reports a folder. - 2026-10-14: git runs in English with `--literal-pathspecs` and without optional locks. Paths stay git's bytes until shown, so a name not in UTF-8 still names its own file; off Unix git writes UTF-8. Status reads porcelain v2. Paths to stage and unstage go in on stdin, NUL-ended (`--pathspec-from-file`), so any name and any count pass; an empty list is refused, since git would take it as the whole tree. Unstaging is `git reset`, which works before the first commit, and a staged rename unstages with its old path; a copy is a new file, its source no part of it. A diff is git's own, without external tools or text converters, a conflict against HEAD, our side, so a file we deleted reads as new; it is read into Ely's stretches; a binary file has none, and an untracked folder none either. Without an index git exits 1 for a difference and a failure alike; only a difference prints. What git warns of while it works goes to the log. The log needs a commit at HEAD: git refuses it before the first. - 2026-10-14: the Git panel shows HEAD's branch, or its short commit while detached; Ely's `ChangesList` of conflicted, staged, unstaged and untracked files; Ely's `CommitInput`; Ely's `CommitList` of the last 50 commits. A row's press opens its diff in Ely's fullscreen `Dialog` under the title bar, in Ely's `DiffViewer`, unified or split; a binary file, an untracked folder and a change of no line say so instead. Every control Ely draws works: stage, unstage, stage all, unstage all, amend, and discard, which asks first in Ely's `ConfirmDialog`: an edit goes back to the index (`git restore`); a new file, untracked or only intended to be added, is deleted (`git clean`, `git rm -f`). Git's clean passes over a repository of its own without a word, so a clean that removed nothing is an error. Actions run off the main thread, each after the one asked before it; a failure shows in Ely's `Alert` with git's words until dismissed or the next action, and after any action Files reads its marks again. A folder change drops the open diff, and one still loading, and blanks the view unless the new folder lies inside the shown work tree, so a cd within it does not blink; no action runs until the shown folder has read, so none reaches the repository left behind. A commit empties only the fields that still hold what it committed, and only once the shown folder has read and is in the same repository: a draft typed while it ran, or while a folder read, stays. A path shows on one line and once: as it is, or quoted as git quotes a name with control characters, quotes, backslashes or bytes not in UTF-8; a folder drops git's trailing slash, since no file shares its name. A section lists 500 files at most, and a line says how many are listed: Ely's list draws every row, and gpui copies every row's hitboxes and paint each frame, so 3000 rows halved the terminal's frames. The Git body is a cached view, so a terminal's output does not draw it again; it comes back from the Files tab with its lists scrolled to the top, since a view cached while hidden drops its widgets' state. Files stays uncached, so its tree keeps its open folders: Ely's `Tree` draws only the rows in view. - 2026-10-14: Cmd-, opens the settings window, or brings the open one forward, and Cmd-W closes it; off macOS Ctrl-, and Ctrl-Shift-W, as Ghostty's. Categories sit in Ely's `SettingsLayout`, rows in its `SettingsRow`; a category joins the list with its rows. Each change goes through `SettingsStore::update`, which checks and saves, and every view reads the global, so it applies at once. A shell saves only when it names a program the running user may run, as the system's `access` answers, ACLs too: empty for the login shell, a file at a full path, or a name found in one of `PATH`'s full folders; a relative folder counts for none, since it would hang on each session's own folder. On Windows a name without an extension means its `.exe`, as `CreateProcess` reads it; a name with one keeps it. A program whose path is not UTF-8 counts for none: a session starts its program by text. A session runs that same full path, so the check and the start agree; else the row says why and the saved one stays, since a bad shell stops the next start. The window takes focus inside when it opens, so its keys find its context. The app quits with its last terminal window: settings alone hold no terminal. @@ -87,3 +87,4 @@ ELYTerminal. A terminal on GPUI and Ely components. `PRD.md` says what it does; - 2026-10-15: a release is a Gitea release tagged `vVERSION` with three builds, each from its script: the macOS disk image for Apple silicon, a Windows zip and a Linux tarball for x86_64. Each window names itself `com.zacharyzhang.elyterminal` through `Window::set_app_id` as it opens: on Linux that is Wayland's app id and X11's `WM_CLASS`, which the desktop entry's name and `StartupWMClass` match, so a dock pairs the window with the icon; gpui's X11 windows read no app id from `WindowOptions`. - 2026-10-15: on Windows a release build is a GUI program, so no console window opens beside it; a debug build keeps its console for the log. git then starts with `CREATE_NO_WINDOW`, or each call would open a console. Windows builds link the C runtime statically, so the executable needs no Visual C++ redistributable. gpui's build script embeds its manifest, PerMonitorV2 DPI awareness among it; `build.rs` adds the icon as resource 1, which gpui loads for each window, and stops the build when no resource compiler is found. - 2026-10-15: the Linux tarball builds on Debian 12, in `rust:1.95.0-bookworm`: a binary asks for the glibc symbol versions it was linked against, so it is linked against an old glibc, not the builder's. `scripts/linux-tar.sh` prints the glibc its build asks for, and the README names it: 2.35 for v0.1.0, which runs on Ubuntu 22.04. +- 2026-10-15: on Windows gpui answers the system's hit test from the last frame: under the pointer, a `WindowControlArea::Drag` hitbox that nothing occludes makes it the caption, and Windows keeps the press, so no click reaches the app. In v0.1.0 every title bar control was dead so. Ely now occludes the drag region under its window buttons, which then answer as close, maximize and minimize, and under each column header's item row, whose controls get plain presses; the row stays the scroll container, and its title is a drag region of its own. diff --git a/Cargo.lock b/Cargo.lock index 82ea098..d93b2bd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1701,7 +1701,7 @@ checksum = "0e9c71c2167ca323c882b99918929403426e2373ea17242ff5653e0d5e1058be" [[package]] name = "ely-gpui-component" version = "0.1.1" -source = "git+https://git.zacharyzhang.com/ZacharyZhang-NY/Ely-GPUI-Components.git?rev=a2cb531e06e15f9e15d5b44a83f82beddb4888f9#a2cb531e06e15f9e15d5b44a83f82beddb4888f9" +source = "git+https://git.zacharyzhang.com/ZacharyZhang-NY/Ely-GPUI-Components.git?rev=cdc5ce8247b58a876ffed9626953c3cb490a67ed#cdc5ce8247b58a876ffed9626953c3cb490a67ed" dependencies = [ "alacritty_terminal", "anyhow", @@ -1747,7 +1747,7 @@ dependencies = [ [[package]] name = "elyterminal" -version = "0.1.0" +version = "0.1.1" dependencies = [ "alacritty_terminal", "anyhow", diff --git a/Cargo.toml b/Cargo.toml index 92ef04a..0641330 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "elyterminal" -version = "0.1.0" +version = "0.1.1" edition = "2024" rust-version = "1.95" license = "MIT" @@ -12,7 +12,7 @@ publish = false alacritty_terminal = { version = "=0.26.0", default-features = false } anyhow = "1" dirs = "7" -ely-gpui-component = { git = "https://git.zacharyzhang.com/ZacharyZhang-NY/Ely-GPUI-Components.git", rev = "a2cb531e06e15f9e15d5b44a83f82beddb4888f9" } +ely-gpui-component = { git = "https://git.zacharyzhang.com/ZacharyZhang-NY/Ely-GPUI-Components.git", rev = "cdc5ce8247b58a876ffed9626953c3cb490a67ed" } env_logger = "0.11" futures = "0.3" gethostname = "1.1.0" diff --git a/PRD.md b/PRD.md index 055d2c4..d5d952a 100644 --- a/PRD.md +++ b/PRD.md @@ -61,6 +61,7 @@ Ghostty is the bar for what the terminal does. ELYTerminal owns its engine on `a - The sessions column and the right column toggle, each by a button at its end of the title bar or by key, and keep their state in settings, so the window opens as it was left. - The title shows the active session's title, and no other text. - The title bar is transparent, as Ghostty's: the window's background, opacity and blur run under it, so it wears the terminal's color. +- Every control in the title bar takes its own press on every platform, the window buttons Ely draws on Windows and Linux too. ## 7. Sessions panel @@ -71,7 +72,7 @@ Ghostty is the bar for what the terminal does. ELYTerminal owns its engine on `a ## 8. Right panel: Files -- A switch at the top: Git, Files. +- A switch at the top, below the title bar: Git, Files. - Files shows the active session's working directory as a tree, folders first, Git status colors on names. - Double press: a folder opens, a file opens in the system's default app. - It reloads when the session reports a new directory and at each prompt. diff --git a/TASKS.md b/TASKS.md index 0963394..40995c8 100644 --- a/TASKS.md +++ b/TASKS.md @@ -68,3 +68,7 @@ Progress truth. Top to bottom, one at a time. A task is ticked only after its co ## 15. Release - [x] T25 Windows zip with the icon in the executable, Linux tarball with the icon and a desktop entry, the app id on Linux windows, their scripts; release v0.1.0 on Gitea with the three builds. + +## 16. The title bar on Windows + +- [ ] T26 On Windows every title bar control takes its press, Ely's window buttons too: in Ely, window buttons and header items occlude the drag region; the Git and Files switch moves below the title bar; release v0.1.1 with the three builds. diff --git a/src/workspace.rs b/src/workspace.rs index 1b2f170..494b5d8 100644 --- a/src/workspace.rs +++ b/src/workspace.rs @@ -332,10 +332,7 @@ fn column_toggle( .variant(ButtonVariant::Ghost) .size(ControlSize::Sm) .tooltip(tip) - .on_click(move |_, _, cx| { - cx.stop_propagation(); - workspace.update(cx, |this, cx| this.toggle(flip, cx)); - }) + .on_click(move |_, _, cx| workspace.update(cx, |this, cx| this.toggle(flip, cx))) } impl Render for Workspace { @@ -379,7 +376,6 @@ impl Render for Workspace { .size(ControlSize::Sm) .tooltip("New session") .on_click(move |_, window, cx| { - cx.stop_propagation(); workspace.update(cx, |this, cx| this.new_session(window, cx)) }); shell = shell.column( @@ -410,11 +406,6 @@ impl Render for Workspace { .segment("files", "Files", None) .size(ControlSize::Sm) .on_change(|value, _, cx| show_tab(value, cx)); - // Its presses stay off the header, whose double press zooms. - let switch = div() - .id("panel-switch") - .on_click(|_, _, cx| cx.stop_propagation()) - .child(switch); let quiet = self .sessions .get(self.active) @@ -433,8 +424,9 @@ impl Render for Workspace { shell = shell.column(terminal).column( ShellColumn::new("panel") .width(width) - .leading(switch) .action(right) + // Below the title bar, clear of Windows' caption buttons. + .child(div().flex().px_2().pt_2().child(switch)) .children(quiet.then(caption)) .child(body(tab == PanelTab::Git).child(self.git.clone().cached(fill()))) .child(body(tab == PanelTab::Files).child(self.files.clone())), diff --git a/tasks/acceptance.md b/tasks/acceptance.md index 55ac0e9..e579fe6 100644 --- a/tasks/acceptance.md +++ b/tasks/acceptance.md @@ -70,6 +70,7 @@ What no check reached: no one has looked at the window on Windows, which CI buil | Columns toggle and keep their state | T11, T23 | a click on each title bar button hid its column and saved it, and a restart opened the window with both hidden (147 columns against 87); Cmd-B with the app active; Ctrl-Shift-B on a real X11 window | Pass | | The title shows the session's title, nothing else | T11 | log: "the title is", per session shown | Pass | | The title bar is transparent | T11, T19 | T19 review | Pass | +| Every title bar control takes its press, Ely's window buttons on Windows and Linux too | T26 | Ely `cdc5ce8`: `window_controls_occlude_what_lies_under` (each style; before, the box under Windows' caption buttons took the press), `a_wobbling_press_on_a_slot_moves_no_window` (before, the window moved), `a_wobbling_press_on_a_column_title_moves_the_window`; gpui's Windows hit test reads only unoccluded hitboxes; not yet seen on a Windows screen | Partial | ## 7. Sessions panel @@ -84,7 +85,7 @@ What no check reached: no one has looked at the window on Windows, which CI buil | PRD line | Task | Evidence | Status | |---|---|---|---| -| Git and Files switch | T13 | T13 review | Pass | +| A switch at the top, below the title bar: Git, Files | T13, T26 | T13 review; a capture on X11 shows it below the title bar, the right toggle and Ely's window buttons in the bar above | Pass | | The session's folder as a tree, folders first, Git colors | T13 | `files::tests` | Pass | | Double press opens a folder, or a file in the default app | T13 | T13 review, with a logging `open` stub | Pass | | Reloads on a new folder and at each prompt | T13 | log: "the tree shows", per folder reported | Pass |