Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 30 additions & 2 deletions app/src/workspace/view/vertical_tabs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1953,11 +1953,16 @@ fn render_tab_group_internal(
rows.finish()
};

let show_header = should_show_tab_group_header(
has_custom_title,
is_being_renamed,
visible_pane_ids.len(),
);
let group_content = if uses_outer_group_container {
let mut group = Flex::column()
.with_main_axis_size(MainAxisSize::Min)
.with_cross_axis_alignment(CrossAxisAlignment::Stretch);
if has_custom_title || is_being_renamed {
if show_header {
group.add_child(render_group_header(
GroupHeaderProps {
tab_index,
Expand All @@ -1970,7 +1975,6 @@ fn render_tab_group_internal(
));
}

let show_header = has_custom_title || is_being_renamed;
let mut body_padding = Padding::uniform(0.)
.with_left(GROUP_HORIZONTAL_PADDING)
.with_right(GROUP_HORIZONTAL_PADDING)
Expand Down Expand Up @@ -2950,6 +2954,30 @@ fn uses_outer_group_container(display_granularity: VerticalTabsDisplayGranularit
matches!(display_granularity, VerticalTabsDisplayGranularity::Panes)
}

/// Decides whether to render the tab-group header above a multi-row group in
/// `Panes` granularity.
///
/// The header is shown when:
/// * the tab has a user-set custom title (rename flow), or
/// * the tab is currently being renamed (inline editor), or
/// * the tab contains more than one visible pane.
///
/// The third condition is what fixes issue #9098: previously the header was
/// only shown when a custom title existed, so multi-pane tabs with auto-
/// generated names (the AI/CLI session naming flow) rendered without any
/// tab-level identifier — only their first row's title was visible, which
/// looked identical for every tab and made the bar appear "nameless".
/// Single-pane groups in `Panes` mode still omit the header because the
/// single row already shows the pane title (avoids duplicating the same
/// text immediately above itself).
fn should_show_tab_group_header(
has_custom_title: bool,
is_being_renamed: bool,
visible_pane_count: usize,
) -> bool {
has_custom_title || is_being_renamed || visible_pane_count > 1
}

fn search_fragments_contain_query(fragments: &[String], query_lower: &str) -> bool {
fragments
.iter()
Expand Down
59 changes: 58 additions & 1 deletion app/src/workspace/view/vertical_tabs_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@ use super::{
non_terminal_search_text_fragments, pane_ids_for_display_granularity,
pane_search_text_fragments, preferred_agent_tab_titles, search_fragments_contain_query,
select_summary_pane_kind_icons, should_keep_detail_sidecar_visible_for_mouse_position,
summary_overflow_count, summary_search_text_fragments, terminal_kind_badge_label,
should_show_tab_group_header, summary_overflow_count, summary_search_text_fragments,
terminal_kind_badge_label,
terminal_primary_line_data, terminal_pull_request_badge_label, terminal_search_text_fragments,
terminal_title_fallback_font, uses_outer_group_container, visible_pane_ids_for_detail_target,
vtab_diff_stats_text, AgentTabTextPreference, SummaryPaneKind, SummaryPaneKindIcons,
Expand Down Expand Up @@ -589,6 +590,62 @@ fn tabs_granularity_does_not_use_outer_group_container() {
));
}

// Regression coverage for #9098 ("Tab names not rendered in tab bar, only
// first tab shows name"). The header gate previously read `has_custom_title
// || is_being_renamed`, which collapsed to `false` for every tab without a
// user-set rename — leaving multi-pane tabs with auto-generated names
// looking like they had no tab label at all. The new gate keeps the existing
// triggers and adds "any multi-pane tab", so every multi-pane group has a
// stable tab-level identifier in `Panes` granularity.
#[test]
fn tab_group_header_shows_for_custom_title() {
assert!(should_show_tab_group_header(true, false, 1));
assert!(should_show_tab_group_header(true, false, 3));
}

#[test]
fn tab_group_header_shows_while_renaming() {
// The inline rename editor must always be reachable, even on
// single-pane tabs with no prior custom title.
assert!(should_show_tab_group_header(false, true, 1));
}

#[test]
fn tab_group_header_shows_for_multi_pane_tabs_without_custom_title() {
// The #9098 case: an auto-named multi-pane tab. Each row only shows the
// per-pane title (e.g. `travelplan` + `main`), so without a group header
// there is no way to tell two such tabs apart in the sidebar.
assert!(should_show_tab_group_header(false, false, 2));
assert!(should_show_tab_group_header(false, false, 5));
}

#[test]
fn tab_group_header_hidden_for_single_pane_without_custom_title() {
// Single-pane groups already surface the pane title in their only row.
// Rendering the same string again as a header would duplicate it
// immediately above itself, so the gate stays closed in this shape.
assert!(!should_show_tab_group_header(false, false, 1));
// Defensive: `0` should not crash or accidentally render a header for
// an empty group (this shape shouldn't reach the renderer in practice,
// but the helper is total and stays closed).
assert!(!should_show_tab_group_header(false, false, 0));
}

#[test]
fn tab_group_header_distinguishes_two_auto_named_multi_pane_tabs() {
// Models the screenshot in #9098: tab 1 has a custom title
// ("Humanfigure"), tabs 2 and 3 are auto-named multi-pane groups
// ("travelplan + main", "deponti + release/development"). Before the
// fix only tab 1 showed a header; after the fix every multi-pane tab
// gets one so the user can tell them apart at a glance.
let renders_header: Vec<bool> = vec![
should_show_tab_group_header(true, false, 2), // tab 1
should_show_tab_group_header(false, false, 2), // tab 2
should_show_tab_group_header(false, false, 2), // tab 3
];
assert_eq!(renders_header, vec![true, true, true]);
}

#[test]
fn terminal_primary_line_prefers_cli_agent_display_title() {
let line = terminal_primary_line_data(
Expand Down