From a69beaded98be60a5fe67c1209b726b3ba259d14 Mon Sep 17 00:00:00 2001 From: l0ng-ai <24760907+l0ng-ai@users.noreply.github.com> Date: Sun, 26 Jul 2026 16:56:03 +0800 Subject: [PATCH 1/3] fix(right-panel): put the window controls back in the window's corner MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Off macOS the detail panel was a full-height column beside the title bar, so the bar — which lays out the ─ ▢ ✕ group at *its* own right end — ended before the panel did, stranding the window controls mid-window with the panel's grey to their right. On macOS the same layout is right: the traffic lights are on the left, over the rail, and nothing collides. So off macOS the bar now spans the panel too, reaching the real top-right corner, and the panel hangs below it: - The caption row over the panel is painted in the panel's own surface, so the column still reads as one continuous sidebar from the top of the window rather than starting 40px down in a different colour. - The corner chrome (detail-panel toggle, workspace chip) stays in the strip, but over the panel it aligns with the column it sits on — pinned to the panel's leading inset instead of hugging the controls. - The panel's tab tiles no longer earn a row of their own. Under a caption row that already carries chrome they made three stacked headers before a single line of content, so they move into the section header the panel was drawing anyway: name and count at one end, the four tiles at the other, a hairline under it. A tab's own control (Files' dotfile toggle) still sits on that line, ahead of the tiles. macOS keeps the full-height panel column with its own title-bar-height top zone, drag region and corner chrome, unchanged. Verified on Windows 11 in both tab-bar modes, panel open and closed, and with the code overlay open (which now stops under the bar, so the controls stay clickable and its header lines up with the panel's). --- src/ui/app.rs | 161 ++++++++++++++++++++++++++---------------- src/ui/right_panel.rs | 100 ++++++++++++++++++-------- src/ui/tab_strip.rs | 41 +++++++++-- 3 files changed, 207 insertions(+), 95 deletions(-) diff --git a/src/ui/app.rs b/src/ui/app.rs index 42aab1bc..5a3f3563 100644 --- a/src/ui/app.rs +++ b/src/ui/app.rs @@ -166,6 +166,12 @@ pub(crate) fn tile_trailing_inset_sm() -> f32 { /// here, not at the window edge. pub(crate) const TITLE_BAR_LEAD: f32 = if cfg!(target_os = "macos") { 80. } else { 12. }; +/// What the bar reserves at its *trailing* edge for the window controls: three +/// 34px tiles (─ ▢ ✕) off macOS, nothing on macOS (the traffic lights are on the +/// left, and `TITLE_BAR_LEAD` covers them). Anything in the bar that has to line +/// up with a column below it measures from the window edge minus this. +pub(crate) const WINDOW_CONTROLS_W: f32 = if cfg!(target_os = "macos") { 0. } else { 102. }; + /// Left offset for the tile group that sits beside the window controls. /// /// On macOS the thing that can collide with the traffic lights is the tile's @@ -5165,68 +5171,103 @@ impl Render for Tty7App { pair.into_iter().filter_map(|(_, el)| el).collect() }; - // The two layouts. Horizontal: a column of [title bar / body]. - // Vertical (default): the rail is a full-height *left column* that reaches the very - // top of the window — the traffic lights sit on its surface — with the - // title strip and terminal stacked in the right column. That way the rail - // surface has no seam with the title bar and reads as one continuous - // panel. - // The right detail panel is a full-height column, not a box under the title - // bar: it carries its own title-bar-height top zone (tab row + the window's - // corner chrome) exactly like the rail does on the left, so its surface - // runs unbroken from the very top of the window. Anything less leaves a - // horizontal seam where the panel's grey starts under the terminal's bar. + // The layout. The rail (vertical mode) is a full-height *left column* that + // reaches the very top of the window — the traffic lights sit on its + // surface — with the title strip and terminal stacked in the right column. + // That way the rail surface has no seam with the title bar and reads as one + // continuous panel. + // + // The right detail panel does the same on macOS: a full-height column + // carrying its own title-bar-height top zone (tab row + the window's corner + // chrome), so its surface runs unbroken from the very top of the window. + // + // Off macOS it can't. The window controls (─ ▢ ✕) are laid out by the title + // bar itself, at *its* right end, so a full-height panel beside the bar + // strands them mid-window with the panel's grey to their right. There the + // bar spans the panel too — reaching the real top-right corner, where + // Windows and Linux users expect the controls — and the panel hangs below + // it, VS Code style. The panel's tab row then sits on the panel (no longer + // in the caption row), and the corner chrome stays in the strip. let right_panel = self.render_right_panel(window, cx); - let main_layout = match sidebar { - Some(sidebar) => div() - .flex_1() - .min_h_0() - .w_full() - .flex() - .flex_row() - .child(sidebar) - .child( - div() - .flex_1() - .min_w_0() - .flex() - .flex_col() - // Anchor for the code overlay: it fills this column - // (title strip + body) — and, since the panel is a sibling - // rather than a child, stops short of the panel for free. - .relative() - .child(title_bar) - .child(body_area) - .children(overlays), - ) - .when_some(right_panel, |this, panel| this.child(panel)) - .into_any_element(), - // Horizontal-tabs mode has no rail, but the panel is still a column - // beside the stacked [title bar / body], for the same reason: it has to - // own its own top zone to read as one surface. - None => div() - .flex_1() - .min_h_0() - .w_full() - .flex() - .flex_row() - .child( - div() - .flex_1() - .min_w_0() - .flex() - .flex_col() - .relative() - .child(title_bar) - .child(body_area) - // Both overlays cover the whole window face here (their - // content pads down past the traffic lights — see - // `render_code_overlay` and `diff_header`). - .children(overlays), - ) - .when_some(right_panel, |this, panel| this.child(panel)) - .into_any_element(), + let panel_below_title_bar = right_panel.is_some() && !cfg!(target_os = "macos"); + // Which host the bar goes to: the terminal column's first child, or the + // spanning row above [terminal | panel]. + let (column_title_bar, spanning_title_bar) = if panel_below_title_bar { + (None, Some(title_bar)) + } else { + (Some(title_bar), None) }; + // The terminal column, and the anchor for both overlays: they fill it — + // and, since the panel is a sibling rather than a child, stop short of the + // panel for free. With the bar spanning above, they stop short of it too, + // which keeps the native controls clickable while an overlay is open and + // lines the overlay's own header row up with the panel's tab row. + let terminal_column = div() + .flex_1() + .min_w_0() + .flex() + .flex_col() + .relative() + .when_some(column_title_bar, |this, bar| this.child(bar)) + .child(body_area) + .children(overlays); + let panel_row = div() + .flex_1() + .min_h_0() + .min_w_0() + .flex() + .flex_row() + .child(terminal_column) + .when_some(right_panel, |this, panel| this.child(panel)); + let main_layout = div() + .flex_1() + .min_h_0() + .w_full() + .flex() + .flex_row() + .when_some(sidebar, |this, sidebar| this.child(sidebar)) + .child(match spanning_title_bar { + Some(bar) => div() + .flex_1() + .min_w_0() + .flex() + .flex_col() + .child( + // The bar's own band over the panel, painted in the panel's + // surface so the column still reads as one continuous + // sidebar from the very top of the window — the rail's + // trick, kept now that the tab row moved off the caption + // line. Without it the panel's grey started 40px down and + // the corner tore into two colours. + // + // A sibling *under* the transparent bar rather than padding + // inside it: the ─ ▢ ✕ group is the bar's own last child, so + // nothing laid out in the bar can get behind the controls, + // and only a layer below can carry a surface under them. + // Same width and left border as the panel, both read from + // `right_panel_px`, so the edge stays in register through a + // resize drag. + div() + .relative() + .flex_none() + .child( + div() + .absolute() + .top_0() + .bottom_0() + .right_0() + .w(px(self.right_panel_px(window, cx))) + .bg(cx.theme().sidebar) + .border_l_1() + .border_color(cx.theme().sidebar_border), + ) + .child(bar), + ) + .child(panel_row) + .into_any_element(), + None => panel_row.into_any_element(), + }) + .into_any_element(); // The real window background paint: gradient-aware and opacity-carrying // (see `theme::window_background`), plus the theme's optional background diff --git a/src/ui/right_panel.rs b/src/ui/right_panel.rs index df97ac7c..2ee6d791 100644 --- a/src/ui/right_panel.rs +++ b/src/ui/right_panel.rs @@ -2,12 +2,14 @@ //! rather than what it's printing — session facts, its working-tree diff, and //! its file tree. //! -//! It splits across two hosts on purpose. The **tab row lives in the title bar** -//! (built in [`tab_strip`](crate::ui::tab_strip)), so the panel's controls sit on -//! the same line as the window's own chrome instead of stacking a second 40px bar -//! under it; the **body** is this module's column inside `body_area`. The two are -//! kept in register by both measuring from `Config::right_panel_width`, so the -//! tabs sit exactly over the content they switch. +//! Its tab row has two homes. On macOS it is the panel's own title-bar-height top +//! zone, level with the window's chrome, so the column runs unbroken from the top +//! of the window. Off macOS the title bar has to span the panel (the window +//! controls live at its right end), so the row drops to the panel's second line — +//! Cursor-style — while the caption row above is painted in the panel's surface +//! so the column still reads as one colour. +//! Either way the tiles themselves are built in +//! [`tab_strip`](crate::ui::tab_strip), beside the rest of the window's tiles. //! //! No new source of truth: Info reads the same `TerminalView`/`Tab` accessors the //! sidebar row does, Changes probes the same `git_diff` the diff overlay does, and @@ -182,14 +184,15 @@ impl Tty7App { // bolted under the title bar: its surface runs the full height of // the window, and the tab row sits *on* it rather than on the // terminal's bar above a seam. - .child({ - // The top zone sits level with the real `TitleBar`, but the - // bar only spans the terminal column — so, exactly like the - // rail's top strip (`tab_sidebar`), make this one act like the - // title bar it aligns with: drag to move, double-click to zoom. - // A press arms a flag and the first *move* starts the window - // move, so a plain click on a tab — and a double-click — still - // lands intact; the tabs and corner chrome take their own. + // + // macOS only. Off macOS the bar spans the panel — it has to, or the + // window controls end up stranded mid-window (see `app::render`) — + // and a row of tiles under that caption row was one chrome row too + // many: the panel opened with three stacked headers (caption chrome, + // tab tiles, section title) before any content. So there the tiles + // move into the section header instead (`panel_title`), which is a + // row the panel was drawing anyway. + .children(cfg!(target_os = "macos").then(|| { let should_move = Rc::new(Cell::new(false)); h_flex() .id("right-panel-titlebar-drag") @@ -204,6 +207,14 @@ impl Tty7App { // down a physical pixel the moment the panel opens. .border_b_1() .border_color(cx.theme().transparent) + // The top zone sits level with the real `TitleBar`, but the + // bar only spans the terminal column — so, exactly like the + // rail's top strip (`tab_sidebar`), make this one act like + // the title bar it aligns with: drag to move, double-click + // to zoom. A press arms a flag and the first *move* starts + // the window move, so a plain click on a tab — and a + // double-click — still lands intact; the tabs and corner + // chrome take their own. .window_control_area(WindowControlArea::Drag) .on_mouse_down(MouseButton::Left, { let should_move = should_move.clone(); @@ -227,7 +238,7 @@ impl Tty7App { // The panel is what reaches the window's right edge while // it's open, so it carries the corner chrome. .child(self.window_chrome(window, cx)) - }) + })) .child(body) // The transfers footer is a sibling of the body, not part of any // tab: an SFTP transfer belongs to the pane, so reading Info or @@ -333,14 +344,20 @@ impl Tty7App { (backing, handle) } - /// A section label inside the panel body — the small caps line that names - /// what the icon-only tab row can't. `trailing` carries a tab's own controls - /// where it has any, so they sit on the label's line rather than earning a - /// second header row. /// A tab's header: the name in a weightier small-caps than the old faint /// label, plus an optional live count trailing it (files, commands, changed /// files) so the header states scale at a glance, and an optional control on /// the right. The count is the quiet mono tally the sidebar group headers use. + /// `trailing` carries a tab's own controls where it has any, so they sit on + /// the label's line rather than earning a second header row. + /// + /// Off macOS this row is also the panel's tab switcher: the four tiles ride + /// at its trailing edge, and the row takes the full title-bar height with a + /// hairline under it. The panel there hangs below a caption row that already + /// carries chrome (see `render_right_panel`), and a tile row of its own on top + /// of this one meant three stacked headers before a single line of content — + /// so the two that were saying "this is a header" merge into one that also + /// says which tab you are on. pub(crate) fn panel_title( &self, text: &str, @@ -348,23 +365,37 @@ impl Tty7App { trailing: Option, cx: &mut Context, ) -> AnyElement { + let tabs = (!cfg!(target_os = "macos")).then(|| self.right_panel_tabs(cx)); + let has_trailing = trailing.is_some(); h_flex() .flex_none() - .h(px(32.)) + // Tall enough to seat the chrome-scale tiles when it carries them; + // otherwise the compact label line it has always been. + .h(px(if tabs.is_some() { + crate::ui::app::TITLE_BAR_HEIGHT + } else { + 32. + })) .items_center() - .justify_between() .pl(px(CONTENT_INSET)) // Trailing tiles align on the glyph like every other control in the - // window; a label-only header just takes the plain inset. `_SM` - // because what hangs here is a body-scale tile, whose glyph sits a - // different distance inside its box than the chrome's does. - .pr(px(if trailing.is_some() { - tile_trailing_inset_sm() - } else { - CONTENT_INSET + // window; a label-only header just takes the plain inset. `_SM` for a + // tab's own control, whose glyph sits a different distance inside its + // box than the chrome-scale tab tiles do. + .pr(px(match (&tabs, has_trailing) { + (Some(_), _) => tile_trailing_inset(), + (None, true) => tile_trailing_inset_sm(), + (None, false) => CONTENT_INSET, })) + // The line that separates the header from the tab's content. Only + // where the header is the switcher: a label alone doesn't need ruling + // off from the band it introduces. + .when(tabs.is_some(), |this| { + this.border_b_1().border_color(cx.theme().sidebar_border) + }) .child( h_flex() + .flex_shrink_0() .items_baseline() .gap(px(7.)) .child( @@ -384,7 +415,20 @@ impl Tty7App { ) }), ) + .child(div().flex_1().min_w_0()) .when_some(trailing, |this, t| this.child(t)) + .when_some(tabs, |this, tiles| { + this.child( + h_flex() + .flex_shrink_0() + .items_center() + .gap(px(2.)) + // Clear of a tab's own control where there is one; flush + // against the label's spring where there isn't. + .when(has_trailing, |this| this.ml(px(6.))) + .children(tiles), + ) + }) .into_any_element() } diff --git a/src/ui/tab_strip.rs b/src/ui/tab_strip.rs index c5e20dd6..93cc3af5 100644 --- a/src/ui/tab_strip.rs +++ b/src/ui/tab_strip.rs @@ -1107,12 +1107,21 @@ impl Tty7App { } else { (window.viewport_size().width - px(114.)).max(px(140.)) }; + // Off macOS the bar spans the detail panel (see `app::render`). The corner + // chrome then sits *over the panel's surface*, so it stops hugging the + // window controls and aligns with the column it is painted on: a block as + // wide as the slice of the panel the bar can reach — panel width less the + // control group, less the panel's own left border — with the tiles packed + // at its leading edge, on the same inset as everything else in the panel. + let chrome_band_w = (!cfg!(target_os = "macos") && self.right_panel_open(cx)).then(|| { + (self.right_panel_px(window, cx) - crate::ui::app::WINDOW_CONTROLS_W - 1.).max(0.) + }); // The "+" and the right-edge overflow "⋯" (30px each), their surrounding // gaps, and the strip's own left/right padding all live *outside* the // clipped chip row — reserve that whole footprint here so the fixed chrome // never overflows the strip box (which would eat the "⋯"'s right inset and // shove it into the window corner) and cap the chip row at the remainder. - let chips_avail = (strip_w - px(100.)).max(px(80.)); + let chips_avail = (strip_w - px(100.) - px(chrome_band_w.unwrap_or(0.))).max(px(80.)); // Only the chip row clips; a crowded row shrinks its chips (down to their // `min_w`) and truncates their labels rather than pushing the "+" away. let mut chips = h_flex() @@ -1532,11 +1541,14 @@ impl Tty7App { }); let panel_open = self.right_panel_open(cx); - // The window's right-corner chrome. When the panel is open it lives on the - // *panel's* top zone (the panel is what reaches the window's right edge - // then) exactly like the rail's controls live on the rail; the strip only - // carries it while the panel is closed. - let right_chrome = (!panel_open).then(|| self.window_chrome(window, cx)); + // The window's right-corner chrome. On macOS, when the panel is open it + // lives on the *panel's* top zone (the panel is what reaches the window's + // right edge then) exactly like the rail's controls live on the rail; the + // strip only carries it while the panel is closed. Off macOS the bar spans + // the panel (the window controls are at its right end — see `app::render`), + // so the strip always reaches the right edge and always carries the chrome. + let right_chrome = + (!panel_open || !cfg!(target_os = "macos")).then(|| self.window_chrome(window, cx)); // Outer strip: the clipping chip row and the always-visible "+" anchored // left, the overflow "⋯" pushed to the right edge by a flexible spacer. @@ -1567,7 +1579,22 @@ impl Tty7App { // leaving just the "⋯" overflow menu on a thin strip. .when(show_chips, move |this| this.child(add_button)) .child(div().flex_1()) - .when_some(right_chrome, |this, chrome| this.child(chrome)) + .when_some(right_chrome, |this, chrome| match chrome_band_w { + // Over the panel: left-aligned on the panel's content edge, and + // wide enough that its own right edge lands where the window + // controls start. + Some(w) => this.child( + h_flex() + .flex_none() + .w(px(w)) + .items_center() + .pl(px(tile_trailing_inset())) + .child(chrome), + ), + // Over the terminal: pinned to the strip's trailing edge, which is + // the window's right edge (or the controls' left edge off macOS). + None => this.child(chrome), + }) } } From 6185d625ccc1af3a6a26a4c4d46f40268be9c8da Mon Sep 17 00:00:00 2001 From: l0ng-ai <24760907+l0ng-ai@users.noreply.github.com> Date: Sun, 26 Jul 2026 17:26:14 +0800 Subject: [PATCH 2/3] fix(right-panel): stop drawing the panel toggle as selected MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The rail's toggle has never lit up when the rail is out, and for the same reason the detail panel's shouldn't: the panel being open is already on screen — it *is* the panel — so a selected capsule only restates it, and with one toggle lit and the other not, a chrome tile's fill stopped meaning anything in particular. The state moves to the tooltip's verb (Show / Hide Detail Panel), which is what the rail's toggle already does. --- src/ui/tab_strip.rs | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/src/ui/tab_strip.rs b/src/ui/tab_strip.rs index 93cc3af5..11c4c84d 100644 --- a/src/ui/tab_strip.rs +++ b/src/ui/tab_strip.rs @@ -631,14 +631,23 @@ impl Tty7App { .when(!cfg!(target_os = "macos"), |this| this.pr_1()) .child( div().occlude().flex_shrink_0().child( + // Never drawn selected, exactly like the rail's own toggle + // (`tab_sidebar`): the panel being open is already on screen — + // it *is* the panel — so a lit capsule only restates it, and + // the two panel toggles would disagree about what a chrome tile + // means. The state lives in the tooltip's verb instead. chrome_tile( Button::new("titlebar-right-panel") .icon(Icon::empty().path("icons/panel-right.svg")), - panel_open, + false, cx, ) .rounded_lg() - .tooltip("Detail Panel") + .tooltip(if panel_open { + "Hide Detail Panel" + } else { + "Show Detail Panel" + }) .on_click(cx.listener(|this, _, _window, cx| { this.toggle_right_panel(cx); })), From 1eb50fbdbae240c0151d4b4dd2be0fb4681375ea Mon Sep 17 00:00:00 2001 From: l0ng-ai <24760907+l0ng-ai@users.noreply.github.com> Date: Sun, 26 Jul 2026 18:10:31 +0800 Subject: [PATCH 3/3] fix(files): move the dotfile switch into the tree's right-click menu MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The eye tile spent the panel's scarcest row — the header the tab tiles now share — on an option that gets set once and then forgotten. It moves into the menu the tree rows already have, as a row under "Reveal in Finder" whose label says what the click will do ("Show Dotfiles" / "Hide Dotfiles") rather than checking off the current state, since one checked item makes `PopupMenu` reserve an icon gutter on every row in the menu. The SFTP browser's header keeps its own controls: Refresh and the "⋯" menu are actions on a live connection, not a view option. --- src/ui/file_tree.rs | 40 ++++++++++++++++++++++++++++++++++++++-- src/ui/right_panel.rs | 31 ++++--------------------------- 2 files changed, 42 insertions(+), 29 deletions(-) diff --git a/src/ui/file_tree.rs b/src/ui/file_tree.rs index ae4dfa7f..770500a3 100644 --- a/src/ui/file_tree.rs +++ b/src/ui/file_tree.rs @@ -1247,9 +1247,18 @@ impl Tty7App { let app = cx.entity().downgrade(); let path = path.clone(); let is_root = row.is_root; + let show_hidden = self.file_tree.show_hidden; move |menu, _window, cx| { let danger = cx.theme().danger; - Self::tree_row_context_menu(menu, &path, is_dir, is_root, danger, &app) + Self::tree_row_context_menu( + menu, + &path, + is_dir, + is_root, + show_hidden, + danger, + &app, + ) } }); @@ -1279,12 +1288,16 @@ impl Tty7App { out } - /// The per-row right-click menu, mirroring Warp's Project Explorer set. + /// The per-row right-click menu, mirroring Warp's Project Explorer set, plus + /// the tree's one view option (dotfiles) — which lives here rather than as a + /// header button: it is set once and then forgotten, and a tile in the header + /// spends the panel's scarcest row on it forever. fn tree_row_context_menu( menu: PopupMenu, path: &Path, is_dir: bool, is_root: bool, + show_hidden: bool, danger: gpui::Hsla, app: &gpui::WeakEntity, ) -> PopupMenu { @@ -1379,6 +1392,8 @@ impl Tty7App { } })); + menu = menu.separator().item(dotfiles_menu_item(show_hidden, app)); + if !is_root { menu = menu.separator().item( PopupMenuItem::element(move |_window, _cx| { @@ -1397,6 +1412,27 @@ impl Tty7App { } } +/// The tree's dotfile switch as a row of the tree's existing right-click menu. +/// +/// The label states what the click will do rather than checking off the current +/// state: a single checked item makes `PopupMenu` reserve a left icon gutter on +/// *every* row in the menu (see `tab_strip::window_chrome`), and the menu has a +/// dozen rows with nothing to put in one. +fn dotfiles_menu_item(show_hidden: bool, app: &gpui::WeakEntity) -> PopupMenuItem { + let app = app.clone(); + PopupMenuItem::new(if show_hidden { + "Hide Dotfiles" + } else { + "Show Dotfiles" + }) + .on_click(move |_, _window, cx| { + let _ = app.update(cx, |this, cx| { + this.file_tree.show_hidden = !this.file_tree.show_hidden; + cx.notify(); + }); + }) +} + /// The little drag ghost shown while a row is dragged toward a terminal. struct DragGhost { name: String, diff --git a/src/ui/right_panel.rs b/src/ui/right_panel.rs index 2ee6d791..bf38b2af 100644 --- a/src/ui/right_panel.rs +++ b/src/ui/right_panel.rs @@ -432,31 +432,6 @@ impl Tty7App { .into_any_element() } - /// The Files header's one control. No refresh button: the tree runs a - /// recursive filesystem watcher over its roots and invalidates its own caches, - /// so a manual refresh is a button that does what already happened. - fn files_controls(&self, cx: &mut Context) -> AnyElement { - let show_hidden = self.file_tree.show_hidden; - crate::ui::tab_strip::chrome_tile_sized( - Button::new("panel-tree-hidden").icon(Icon::new(IconName::Eye)), - TILE_SIZE_SM, - TILE_GLYPH_SM, - show_hidden, - cx, - ) - .rounded_md() - .tooltip(if show_hidden { - "Hide dotfiles" - } else { - "Show dotfiles" - }) - .on_click(cx.listener(|this, _, _w, cx| { - this.file_tree.show_hidden = !this.file_tree.show_hidden; - cx.notify(); - })) - .into_any_element() - } - /// A tab's filter box — the same borderless magnifier + input the tab rail /// uses, so everything in the window searches the same way. Sits under the /// header rather than in it: it's a full-width control, not a trailing tile. @@ -1334,8 +1309,10 @@ impl Tty7App { return self.render_panel_sftp(host.unwrap_or_default(), window, cx); } - let controls = self.files_controls(cx); - let title = self.panel_title("Files", None, Some(controls), cx); + // No header control: the tree's one view option (dotfiles) is a + // right-click away in the tree itself (`file_tree::dotfiles_menu_item`), + // which is where you are when you want it. + let title = self.panel_title("Files", None, None, cx); let search = self.panel_search(&self.file_search.clone(), cx); let rows = self.render_file_tree_rows(window, cx); v_flex()