12 KiB
APP-3713: Tech Spec — Primary Info Selector in Vertical Tabs Settings Popup
Problem
The vertical tabs settings popup contains a hardcoded "Group panes by" section with a single "Tab" option that has no effect. Per the product spec, this section should be replaced with a "Show first" selector that lets users choose whether terminal pane rows display the terminal command / agent conversation title or the working directory / git branch as the primary (top) line. The setting must affect both expanded and compact view modes and persist across sessions.
Relevant code
app/src/workspace/tab_settings.rs (166-214)—VerticalTabsViewModeenum andTabSettingsgroup; the new setting will live hereapp/src/workspace/view/vertical_tabs.rs (2004-2143)—render_settings_popupand the "Group panes by" / "Tab" UI to be replacedapp/src/workspace/view/vertical_tabs.rs (1395-1447)—render_terminal_row_content(expanded mode), assembles primary + secondary + tertiary linesapp/src/workspace/view/vertical_tabs.rs (1449-1479)—render_terminal_primary_line_for_view, builds the primary line element fromTerminalViewdataapp/src/workspace/view/vertical_tabs.rs (1585-1646)—render_terminal_secondary_line, builds working directory + git branch lineapp/src/workspace/view/vertical_tabs.rs (2183-2230)—render_compact_pane_row, single-line compact rendering for terminal panesapp/src/workspace/action.rs (234-235)—ToggleVerticalTabsSettingsPopupandSetVerticalTabsViewModeactions; new action for setting primary info will follow this patternapp/src/workspace/view.rs (16988-17004)— action handlers for the existing popup actionsapp/src/workspace/view.rs (18467-18487)— popup overlay rendering withDismisswrapperapp/src/workspace/view/vertical_tabs_tests.rs— existing unit tests for primary line data logicapp/src/workspace/action_tests.rs— tests for actionshould_save_app_state_on_action
Current state
Setting and persistence
VerticalTabsViewMode is a two-variant enum (Compact, Expanded) registered as a synced cloud setting in TabSettings. It uses the implement_setting_for_enum! macro with SyncToCloud::Globally(RespectUserSyncSetting::Yes) and hierarchy "appearance.tabs". The new primary info setting will follow this exact pattern.
Settings popup
render_settings_popup builds a fixed-width (200px) popup with:
- A "Group panes by" header (sub-text color, 12px)
- A "Tab" item (checkmark icon + "Tab" label, always selected)
- A divider
- A segmented control for compact/expanded
The "Group panes by" section is purely visual — it dispatches no actions and reads no settings.
Expanded terminal row rendering
render_terminal_row_content builds a three-line column:
- Primary: calls
render_terminal_primary_line_for_view→render_terminal_primary_line, which resolvesTerminalPrimaryLineDatathrough the precedence cascade (conversation title > CLI agent title > terminal title > last command > "New session"), then renders in main-text color. - Secondary: calls
render_terminal_secondary_line(working_directory, git_branch), which renders working directory (start-clipped) + git branch (end-clipped) in sub-text color. - Tertiary: kind badge + right-side badges (unchanged by this feature).
Compact terminal row rendering
render_compact_pane_row calls render_terminal_primary_line_for_view with Some(WarpIcon::Terminal) as a prefix icon, producing a single-line row with the terminal icon + primary line data text. For agent terminals, the status icon comes from the StatusText variant of TerminalPrimaryLineData.
Key render functions and their color contracts
render_terminal_primary_linealways usesmain_text_color.render_terminal_secondary_linealways usessub_text_color.
When swapping lines, we need parameterized color rather than hardcoded main/sub text.
Proposed changes
1. Add VerticalTabsPrimaryInfo setting
In app/src/workspace/tab_settings.rs, add a new enum and register it in TabSettings:
#[derive(Default, Debug, serde::Serialize, serde::Deserialize, PartialEq, Copy, Clone)]
pub enum VerticalTabsPrimaryInfo {
#[default]
Command,
WorkingDirectory,
}
Register with implement_setting_for_enum! using the same sync/hierarchy as VerticalTabsViewMode:
SupportedPlatforms::ALLSyncToCloud::Globally(RespectUserSyncSetting::Yes)hierarchy: "appearance.tabs"
Add vertical_tabs_primary_info: VerticalTabsPrimaryInfo to the TabSettings group.
2. Add SetVerticalTabsPrimaryInfo action
In app/src/workspace/action.rs:
- Add variant
SetVerticalTabsPrimaryInfo(VerticalTabsPrimaryInfo)next toSetVerticalTabsViewMode. - In
should_save_app_state_on_action, add it to thefalsearm (same asSetVerticalTabsViewMode— the setting is persisted via the settings framework, not workspace state).
In app/src/workspace/view.rs, add the handler in the same block as SetVerticalTabsViewMode:
SetVerticalTabsPrimaryInfo(primary_info) => {
let primary_info = *primary_info;
TabSettings::handle(ctx).update(ctx, |settings, ctx| {
let _ = settings.vertical_tabs_primary_info.set_value(primary_info, ctx);
});
ctx.notify();
}
3. Add mouse state handles for option rows
In VerticalTabsPanelState, add two new MouseStateHandle fields for the popup option rows:
command_option_mouse_state: MouseStateHandle,
directory_option_mouse_state: MouseStateHandle,
Initialize with Default::default() in the Default impl.
4. Replace popup "Group by" section with "Show first" section
In render_settings_popup:
Remove: The group_by_header and tab_item variables and their children in the assembled column.
Replace with: A "Show first" header and two clickable option rows.
The header reuses the same styling as the current "Group panes by" header but with text "Show first".
Each option row is a Hoverable wrapping a Flex::row with:
- A 16×16 checkmark icon (
WarpIcon::Checkin main-text color) if selected, or a 16×16Emptyspacer if not. - An 8px gap.
- Label text ("Command / Conversation" or "Directory / Branch") in main-text color, 12px.
On hover: fg_overlay_1 background, pointing hand cursor.
On click: dispatch WorkspaceAction::SetVerticalTabsPrimaryInfo(variant). The popup does not close on click (unlike ToggleVerticalTabsSettingsPopup), so the user sees the checkmark move.
Read the current setting via *TabSettings::as_ref(app).vertical_tabs_primary_info.value() to determine which option gets the checkmark.
The divider and segmented control below remain unchanged.
5. Parameterize text color in terminal line renderers
The existing render_terminal_primary_line hardcodes main_text_color and render_terminal_secondary_line hardcodes sub_text_color. To support swapping, add a text_color: WarpThemeFill parameter to both functions so the caller controls which color each line uses.
render_terminal_primary_line: Add text_color param. Replace main_text_color usage for text rendering with the passed color. The status indicator icon color remains unchanged (it comes from render_status_element and is independent of text color).
render_terminal_secondary_line: Add text_color param. Replace sub_text_color usage with the passed color.
6. Update render_terminal_row_content (expanded mode)
Read the primary info setting:
let primary_info = *TabSettings::as_ref(app).vertical_tabs_primary_info.value();
Branch on the setting to decide line order:
Command(default): Callrender_terminal_primary_line_for_viewwithmain_text_coloras first child,render_terminal_secondary_linewithsub_text_coloras second child. This is the current behavior.WorkingDirectory: Callrender_terminal_secondary_linewithmain_text_coloras first child,render_terminal_primary_line_for_viewwithsub_text_coloras second child. This swaps the line order and colors.
The tertiary line is always appended last, unchanged.
7. Update render_compact_pane_row (compact mode)
Read the primary info setting. For terminal panes:
Command: Current behavior — callrender_terminal_primary_line_for_viewwithSome(WarpIcon::Terminal)prefix icon.WorkingDirectory: Render a single-line row with the terminal icon (or agent status icon) + working directory text (start-clipped). This can be done by building aFlex::rowdirectly:- Add the kind icon (terminal icon for non-agent, status element for agent,
OzCloudfor ambient agent) — same icon logic as the current compact rendering. - Add the working directory text with
ClipConfig::start().
- Add the kind icon (terminal icon for non-agent, status element for agent,
The icon is always determined by pane type / agent state, not by the primary info setting.
8. Update render_terminal_primary_line_for_view
Add a text_color: WarpThemeFill parameter. Pass it through to render_terminal_primary_line.
All existing call sites pass theme.main_text_color(theme.background()) to preserve current behavior unless the caller is in the swapped path.
End-to-end flow
- User clicks the settings icon button in the vertical tabs control bar →
ToggleVerticalTabsSettingsPopupis dispatched →show_settings_popuptoggles totrue→ popup renders viarender_settings_popup. - Popup shows "Show first" header with "Command / Conversation" checked (default). Segmented control below.
- User clicks "Directory / Branch" →
SetVerticalTabsPrimaryInfo(WorkingDirectory)is dispatched →TabSettings.vertical_tabs_primary_infois updated →ctx.notify()triggers re-render. - Popup stays open; checkmark moves to "Directory / Branch".
- All terminal pane rows in the panel re-render: expanded rows swap primary/secondary lines; compact rows show working directory text.
- Setting is persisted to cloud via the settings sync framework.
- On next launch,
TabSettings::as_ref(app).vertical_tabs_primary_info.value()returnsWorkingDirectory, so rows render in the swapped order immediately.
Risks and mitigations
- Color regression: Parameterizing text color introduces risk of passing the wrong color at call sites. Mitigation: only two call sites per function (one for each branch of the setting match). Unit test the data logic; visual testing catches color regressions.
- Compact mode icon/text mismatch: In
WorkingDirectorycompact mode, an agent terminal must still show the status icon (not the terminal icon) but with the working directory text. The icon resolution logic is already separated from the text logic in the current code, so this is straightforward. - Search correctness:
terminal_pane_search_text_fragmentsindexes both the primary text and working directory regardless of display order. No changes needed; search stays correct.
Testing and validation
Unit tests (vertical_tabs_tests.rs)
- Add tests for the "Show first" setting variants confirming
VerticalTabsPrimaryInfo::CommandandVerticalTabsPrimaryInfo::WorkingDirectoryareDefaultandCopy/Clone/PartialEqas expected.
Action tests (action_tests.rs)
SetVerticalTabsPrimaryInfoshould returnfalsefromshould_save_app_state_on_action(same pattern as the existingSetVerticalTabsViewModetest).
Manual validation
Per the product spec validation section: toggle between options in the popup, verify expanded/compact rendering, agent panes, persistence across restart, search correctness, and non-terminal pane immunity.
Follow-ups
- When group-by functionality is implemented in the future, the "Show first" section and the group-by section can coexist in the same popup. The popup layout would then be: "Show first" options → divider → "Group by" options → divider → segmented control.
- A keyboard shortcut for toggling the primary info setting could be added later.