Initial public release of Warp.
Repo-Sync-Origin: warpdotdev/warp-internal@12af1d983b
This commit is contained in:
@@ -0,0 +1,133 @@
|
||||
# Sidecars for Tab Config Menu
|
||||
|
||||
Linear: [APP-3886](https://linear.app/warpdotdev/issue/APP-3886/sidecars-for-tab-config-menu)
|
||||
Figma: [House of Agents – sidecar design](https://www.figma.com/design/CsBdBW4YoLgSAbr5eSkwV6/House-of-Agents?node-id=7877-45160&m=dev)
|
||||
|
||||
## Summary
|
||||
Add a sidecar panel to every actionable item in the tab configs menu that exposes per-item actions: **Make default**, **Edit config**, and **Remove** (**Edit config** and **Remove** will not be shown for non-user-created tab-configs (i.e. terminal & agent)). Unify the "Make default" choice with the existing "Default mode for new sessions" setting so there is a single source of truth for what Cmd+T does. Flatten the Windows-only Terminal shell submenu into top-level menu items so every item can have a sidecar.
|
||||
|
||||
## Problem
|
||||
Users can create tab configs but have no lightweight way to manage them from the menu. Editing requires manually navigating to `~/.warp/tab_configs/`, there is no way to delete a config from the UI, and there is no way to set a tab config as the default Cmd+T action. The "Default mode for new sessions" setting only offers Terminal and Agent, with no way to select a tab config.
|
||||
|
||||
## Goals
|
||||
- Surface "Make default", "Edit config", and "Remove" actions via a sidecar on menu items.
|
||||
- Extend the "Default mode for new sessions" setting to include all tab configs, creating a single source of truth for what Cmd+T opens.
|
||||
- Flatten the Windows Terminal shell submenu into top-level items so every item gets uniform sidecar treatment.
|
||||
- Respect the correct editor setting (`open_code_panels_file_editor`) when opening tab config files.
|
||||
|
||||
## Non-goals
|
||||
- Inline editing of tab config TOML content from within the sidecar.
|
||||
- Drag-to-reorder tab configs in the menu.
|
||||
- A new UI for creating tab configs (the existing "New Tab Config" and "New worktree config" flows remain).
|
||||
- A sidecar for the "New worktree config" submenu item (it keeps its existing repo-list sidecar and is not something a user would set as default).
|
||||
- A sidecar for the "New Tab Config" item (it's a creation action, not a selectable default).
|
||||
|
||||
## User experience
|
||||
|
||||
### Menu structure changes
|
||||
The tab configs menu items become:
|
||||
|
||||
1. **Agent** (if AI enabled)
|
||||
2. **Terminal** (on all platforms; on Windows this will be multiple items for each terminal type, including the default terminal item)
|
||||
3. **Additional shell variants** (Windows only — e.g., PowerShell, CMD — listed as individual top-level items instead of nested in a Terminal submenu)
|
||||
4. **Cloud Oz** (if AI enabled + AgentView + CloudMode flags)
|
||||
5. **User tab configs** (from `~/.warp/tab_configs/` and `~/.warp/default_tab_configs/`)
|
||||
6. Separator
|
||||
7. **New worktree config** (submenu with repo-list sidecar — unchanged)
|
||||
8. **New Tab Config** (creation action — unchanged)
|
||||
|
||||
Items 1–5 each get the new action sidecar. Items 7–8 do not.
|
||||
|
||||
### Sidecar trigger
|
||||
When a user hovers over an actionable item (items 1–5 above), a sidecar panel appears to the right of the menu, following the existing sidecar positioning and safe-zone pattern. The sidecar replaces itself as the user moves between items.
|
||||
|
||||
When the user hovers over a separator, "New worktree config", or "New Tab Config", the action sidecar hides. "New worktree config" continues to show its own repo-list sidecar as it does today.
|
||||
|
||||
### Sidecar layout
|
||||
The sidecar panel contains:
|
||||
1. **Title**: The item name (e.g., "Terminal", "PowerShell", "Oz", or the tab config's `name` field).
|
||||
2. **Subtitle**: For user tab configs, the full path to the `.toml` source file displayed in a subdued/secondary text style (e.g., `~/.warp/tab_configs/my_config.toml`). For built-in items, no subtitle.
|
||||
3. **Buttons**: Varies by item type (see below).
|
||||
|
||||
### Sidecar buttons by item type
|
||||
|
||||
**Built-in items (Terminal, shell variants, Oz, Cloud Oz):**
|
||||
- "Make default" only.
|
||||
|
||||
**User tab configs:**
|
||||
- "Make default"
|
||||
- "Edit config"
|
||||
- "Remove" (destructive/red styling)
|
||||
|
||||
### "Make default" behavior
|
||||
Sets the selected item as the action triggered by Cmd+T (`workspace:new_tab` keybinding).
|
||||
|
||||
This directly writes the **"Default mode for new sessions"** setting (see below). The sidecar choice and the settings page dropdown are always in sync — changing one updates the other.
|
||||
|
||||
- **Terminal**: Sets default to `Terminal`.
|
||||
- **A shell variant** (Windows): Sets default to `Terminal`. (Shell-specific defaults are out of scope; this just ensures Cmd+T opens a terminal.)
|
||||
- **Oz**: Sets default to `Agent`.
|
||||
- **Cloud Oz**: Sets default to `Cloud Agent` (i.e. the tab you open to when you click this option now)
|
||||
- **A user tab config**: Sets default to that config (identified by source file path).
|
||||
|
||||
When Cmd+T fires:
|
||||
- If default is `Terminal` → open a terminal tab (existing behavior).
|
||||
- If default is `Agent` → open an agent tab (existing behavior).
|
||||
- If default is a tab config → open that config using the same flow as clicking it in the menu (params modal if it has params, direct open if not).
|
||||
|
||||
### Extending "Default mode for new sessions"
|
||||
The existing `DefaultSessionMode` enum (`Terminal | Agent`) is extended to also include a tab-config variant that references a config by source file path. The settings dropdown in the AI/session settings page lists:
|
||||
- Terminal
|
||||
- Agent
|
||||
- Every currently-loaded tab config (by name)
|
||||
|
||||
Selecting a tab config from the dropdown sets it as the Cmd+T default, same as clicking "Make default" in the sidecar. The dropdown should update dynamically as tab configs are added/removed (driven by the filesystem watcher).
|
||||
|
||||
If the selected tab config is deleted (file removed from disk), the setting falls back to `Terminal` (or whatever `DefaultSessionMode`'s default is).
|
||||
|
||||
### Visual indicator for current default
|
||||
The menu item that is currently the default shows a **Cmd+T keybinding indicator** (matching the Figma design). This is the same keybinding hint pattern used elsewhere in the menu. Only one item displays this indicator at a time — whichever item is the current default.
|
||||
|
||||
### "Edit config" behavior
|
||||
Opens the tab config `.toml` file in the user's configured editor, respecting the **"Choose an editor to open files from the code review panel, project explorer, and global search"** setting (`open_code_panels_file_editor`), **not** the "Choose an editor to open file links" setting (`open_file_editor`).
|
||||
|
||||
When the editor resolves to Warp, the file opens in a new tab with the file tree open and focused on the config file.
|
||||
|
||||
This editor-setting fix also applies to:
|
||||
- The existing **"New Tab Config"** button at the bottom of the menu.
|
||||
- The **"Open file"** action from tab config error toasts (`OpenTabConfigErrorFile`).
|
||||
|
||||
### "Remove" behavior
|
||||
1. A Warp modal (following existing modal prior art, e.g., the close-session confirmation dialog) appears asking the user to confirm deletion, indicating the config name and that the file will be permanently deleted.
|
||||
2. On confirm, the `.toml` file is deleted from disk.
|
||||
3. The filesystem watcher picks up the deletion and removes the config from the menu.
|
||||
4. If the removed config was the current default, the setting reverts to `Terminal`.
|
||||
5. On cancel, nothing happens.
|
||||
|
||||
### Edge cases
|
||||
- **Default config file deleted externally**: When Cmd+T fires and the stored config path no longer exists, clear the default silently and fall through to `Terminal` behavior. The next watcher event will also clean up the settings dropdown.
|
||||
- **Config parse error after edit**: Handled by the existing error toast mechanism. The sidecar does not need special handling.
|
||||
- **Menu keyboard navigation**: Arrow keys update the sidecar to reflect the currently-selected item, matching the existing sidecar behavior.
|
||||
- **Sidecar safe zone**: Uses the existing safe-zone mechanism so the mouse can travel from the menu to the sidecar without it closing.
|
||||
- **Multiple configs with the same name**: Each is listed separately; the default is identified by file path, not name, so there is no ambiguity.
|
||||
- **Windows Terminal flattening**: Removing the Terminal submenu means `NewSessionSidecarKind::Terminal` and `configure_terminal_new_session_sidecar` are no longer needed. The shell items become regular top-level menu entries.
|
||||
|
||||
## Success criteria
|
||||
1. Hovering over any actionable item (Terminal, shell variants, Oz, user tab configs) in the tab configs menu shows a sidecar.
|
||||
2. The sidecar for user tab configs shows the config name, file path, and three buttons (Make default, Edit config, Remove).
|
||||
3. The sidecar for built-in items shows only "Make default".
|
||||
4. Clicking "Make default" updates the "Default mode for new sessions" setting.
|
||||
5. The settings dropdown in the AI/session settings page lists Terminal, Agent, and all loaded tab configs, and stays in sync with sidecar choices.
|
||||
6. Pressing Cmd+T opens the currently-configured default (terminal, agent, or tab config).
|
||||
7. The default persists across app restarts.
|
||||
8. Clicking "Edit config" opens the `.toml` file using `open_code_panels_file_editor`.
|
||||
9. Clicking "Remove" shows a confirmation dialog; confirming deletes the file and removes the config from the menu.
|
||||
10. If the default config is removed (via UI or externally), Cmd+T reverts to Terminal.
|
||||
11. Keyboard navigation in the menu updates the sidecar.
|
||||
12. "New Tab Config" and tab config error toasts also respect `open_code_panels_file_editor`.
|
||||
13. On Windows, Terminal shell variants appear as individual top-level items instead of a submenu.
|
||||
|
||||
## Validation
|
||||
- **Unit tests**: `DefaultSessionMode` extension stores and retrieves tab config paths correctly. Falls back to `Terminal` when the stored path doesn't exist. `resolve_file_target` for tab config files uses `open_code_panels_file_editor`.
|
||||
- **Manual / computer-use verification**: Open the tab configs menu, hover over items, verify sidecar appears with correct content. Exercise Make default, Edit config, and Remove. Verify Cmd+T behavior changes. Verify settings dropdown stays in sync. Verify Cmd+T reverts after removing the default config.
|
||||
- **Regression**: Existing "New worktree config" sidecar behavior is unaffected. Existing Cmd+T behavior is unchanged when no custom default is set.
|
||||
@@ -0,0 +1,334 @@
|
||||
# Sidecars for Tab Config Menu — Tech Spec
|
||||
|
||||
Linear: [APP-3886](https://linear.app/warpdotdev/issue/APP-3886/sidecars-for-tab-config-menu)
|
||||
Product spec: `specs/APP-3886/PRODUCT.md`
|
||||
|
||||
## Problem
|
||||
The tab configs menu has no per-item management actions. Users cannot set a default, edit, or remove tab configs from within the menu. The `DefaultSessionMode` setting only supports `Terminal | Agent`, with no way to select a tab config. The sidecar infrastructure exists for worktree/shell submenus but not for action panels.
|
||||
|
||||
## Relevant code
|
||||
|
||||
**Tab configs menu & sidecar:**
|
||||
- `app/src/workspace/view.rs:5159` — `unified_new_session_menu_items()` builds the menu (shared by both horizontal and vertical tabs)
|
||||
- `app/src/workspace/view.rs:5280` — `open_tab_configs_menu()` opens the menu, takes `is_vertical_tabs` param for width
|
||||
- `app/src/workspace/view.rs:7838` — `update_new_session_sidecar()` dispatches sidecar content based on hovered item
|
||||
- `app/src/workspace/view.rs:7675` — `configure_terminal_new_session_sidecar()` (Windows-only, to be removed)
|
||||
- `app/src/workspace/view.rs:7731` — `configure_worktree_new_session_sidecar()`
|
||||
- `app/src/workspace/view.rs:20291` — dropdown menu overlay rendering (different anchoring for vertical vs horizontal)
|
||||
- `app/src/workspace/view.rs:20331` — sidecar overlay rendering (shared by both modes, anchored to hovered item label)
|
||||
- `app/src/workspace/view.rs:966` — `new_session_sidecar_menu` field (existing `Menu`-based sidecar)
|
||||
- `app/src/workspace/view.rs:1650` — sidecar menu construction in `build_menus()`
|
||||
- `app/src/workspace/view/vertical_tabs.rs:1031` — `render_new_tab_button()` for vertical tabs (dispatches same `ToggleNewSessionMenu` action)
|
||||
- `app/src/workspace/view/vertical_tabs.rs:766` — `VERTICAL_TABS_ADD_TAB_POSITION_ID`
|
||||
|
||||
**Tab config data model:**
|
||||
- `app/src/tab_configs/tab_config.rs:128` — `TabConfig` struct (no `source_path` field)
|
||||
- `app/src/user_config/util.rs:167` — `parse_tab_config_dir_entry()` (path known here, only stored for errors)
|
||||
- `app/src/user_config/mod.rs:105` — `WarpConfig::tab_configs()` accessor
|
||||
- `app/src/user_config/native.rs:248` — `load_tab_configs()` and filesystem watcher
|
||||
|
||||
**DefaultSessionMode setting:**
|
||||
- `app/src/settings/ai.rs:253` — `DefaultSessionMode` enum (`Terminal | Agent`, derives `Copy`, `EnumIter`)
|
||||
- `app/src/settings/ai.rs:1087` — stored as `default_session_mode_internal` in `AISettings`
|
||||
- `app/src/settings/ai.rs:1227` — `AISettings::default_session_mode()` accessor (gates on AI enabled)
|
||||
- `app/src/settings_view/features_page.rs:3273` — `update_default_session_mode_dropdown()` builds dropdown from `iter()`
|
||||
|
||||
**Cmd+T flow:**
|
||||
- `app/src/workspace/view.rs:18317` — `AddDefaultTab` handler (the Cmd+T entry point; routes based on `DefaultSessionMode`)
|
||||
- `app/src/app_menus.rs:1084` — `open_new_default_tab_or_window()` (macOS native menu callback; always dispatches `CustomAction::NewTab` → `AddDefaultTab`)
|
||||
- `app/src/workspace/view.rs:9637` — `add_new_session_tab_with_default_mode()` (checks `DefaultSessionMode`)
|
||||
- `app/src/workspace/view.rs:5430` — `open_tab_config()` (opens a tab config, shows params modal if needed)
|
||||
|
||||
**Editor setting for opening files:**
|
||||
- `app/src/util/openable_file_type.rs:97` — `resolve_file_target()` (uses `open_file_editor`)
|
||||
- `app/src/util/openable_file_type.rs:112` — `resolve_file_target_with_editor_choice()` (accepts explicit editor choice)
|
||||
- `app/src/util/file/external_editor/settings.rs:66` — `open_code_panels_file_editor` setting
|
||||
- `app/src/workspace/view.rs:5469` — `create_and_open_new_tab_config()` (currently uses wrong setting)
|
||||
|
||||
**Confirmation dialog prior art:**
|
||||
- `app/src/workspace/close_session_confirmation_dialog.rs` — `CloseSessionConfirmationDialog` pattern
|
||||
|
||||
**Windows shell listing:**
|
||||
- `app/src/workspace/view.rs:5191` — `#[cfg(target_os = "windows")]` Terminal submenu parent
|
||||
- `app/src/workspace/view.rs:7675` — `configure_terminal_new_session_sidecar()` (lists shells)
|
||||
|
||||
## Current state
|
||||
|
||||
### Menu structure
|
||||
`unified_new_session_menu_items()` builds the menu. This function is shared across both horizontal and vertical tab bar modes — the same items appear regardless of layout. The menu is opened via `open_tab_configs_menu()` which accepts `is_vertical_tabs` only to adjust the menu width (268px for vertical, default for horizontal).
|
||||
|
||||
Current order:
|
||||
1. Agent (with Cmd+T shortcut label if default is Agent)
|
||||
2. Terminal (submenu on Windows, regular item elsewhere; Cmd+T shortcut if default is Terminal)
|
||||
3. Cloud Oz
|
||||
4. User tab configs (from `WarpConfig::tab_configs()`)
|
||||
5. Separator + "New worktree config" (submenu) + "New Tab Config"
|
||||
|
||||
### Sidecar positioning (horizontal vs vertical)
|
||||
The dropdown menu is positioned differently in each mode:
|
||||
- **Horizontal tabs**: anchored to `NEW_TAB_BUTTON_POSITION_ID` (lower-left of the + button)
|
||||
- **Vertical tabs**: anchored to `VERTICAL_TABS_ADD_TAB_POSITION_ID` (below the + button)
|
||||
|
||||
The sidecar overlay anchors to the hovered menu item's label text, so it's positioned the same way in both modes. The new action sidecar will use the same anchoring mechanism and work in both modes without special handling.
|
||||
|
||||
Note: The vertical tabs panel has its own separate "detail sidecar" (`render_detail_sidecar` in `vertical_tabs.rs`) that shows pane details when hovering rows in the panel. This is a completely different system from the new-session menu sidecar and is unrelated to this feature.
|
||||
|
||||
### DefaultSessionMode
|
||||
A `Copy + EnumIter` enum stored in `AISettings`. The settings dropdown iterates it. `default_session_mode()` returns `Terminal` when AI is disabled, otherwise returns the stored value.
|
||||
|
||||
### TabConfig
|
||||
Has no `source_path` field. The file path is available during parsing but discarded for successfully parsed configs.
|
||||
|
||||
## Proposed changes
|
||||
|
||||
### 1. Add `source_path` to `TabConfig`
|
||||
**File:** `app/src/tab_configs/tab_config.rs`
|
||||
|
||||
Add a skipped field:
|
||||
```rust
|
||||
#[serde(skip)]
|
||||
pub source_path: Option<PathBuf>,
|
||||
```
|
||||
|
||||
**File:** `app/src/user_config/util.rs`
|
||||
|
||||
In `parse_tab_config_dir_entry()`, populate `source_path` on successfully parsed configs:
|
||||
```rust
|
||||
Some(parsed.map(|mut config| {
|
||||
config.source_path = Some(item.path().into());
|
||||
config
|
||||
}).map_err(...))
|
||||
```
|
||||
|
||||
### 2. Extend `DefaultSessionMode` with `TabConfig` and `CloudAgent` variants + companion path setting
|
||||
**File:** `app/src/settings/ai.rs`
|
||||
|
||||
Add `CloudAgent` and `TabConfig` variants to `DefaultSessionMode`:
|
||||
```rust
|
||||
pub enum DefaultSessionMode {
|
||||
#[default]
|
||||
Terminal,
|
||||
Agent,
|
||||
CloudAgent,
|
||||
TabConfig,
|
||||
}
|
||||
```
|
||||
|
||||
This preserves `Copy` and `EnumIter`. No `Shell` variant is needed — shell-specific defaults are handled by the existing `NewSessionShell` setting (see "Setting interaction" below).
|
||||
|
||||
Add a companion setting in `AISettings` to store the tab config file path:
|
||||
```rust
|
||||
default_tab_config_path: DefaultTabConfigPath {
|
||||
type: String,
|
||||
default: String::new(),
|
||||
supported_platforms: SupportedPlatforms::ALL,
|
||||
sync_to_cloud: SyncToCloud::Never,
|
||||
private: false,
|
||||
hierarchy: "general",
|
||||
}
|
||||
```
|
||||
|
||||
`SyncToCloud::Never` because tab config file paths are machine-local.
|
||||
|
||||
The companion is only read when mode is `TabConfig`. For all other modes it's ignored.
|
||||
|
||||
Add a helper on `AISettings`:
|
||||
```rust
|
||||
fn resolved_default_tab_config(&self, app: &AppContext) -> Option<TabConfig>
|
||||
```
|
||||
Reads `default_tab_config_path`, finds the matching `TabConfig` in `WarpConfig::tab_configs()` by `source_path`, and returns it. Returns `None` if the path is empty, the file doesn't exist, or the config isn't loaded (triggers fallback to `Terminal`).
|
||||
|
||||
### Setting interaction: `DefaultSessionMode` × `NewSessionShell`
|
||||
Two settings, two concerns:
|
||||
- **`DefaultSessionMode`** (`Terminal | Agent | CloudAgent | TabConfig`) — controls *what kind of thing* Cmd+T opens.
|
||||
- **`NewSessionShell`** (existing, in `SessionSettings`) — controls *which shell binary* a terminal session uses. Only relevant when the mode resolves to opening a terminal.
|
||||
|
||||
How they interact on Cmd+T:
|
||||
- `Terminal` → opens a terminal tab using whatever `NewSessionShell` is set to.
|
||||
- `Agent` → opens agent view. `NewSessionShell` irrelevant.
|
||||
- `CloudAgent` → opens ambient agent tab. `NewSessionShell` irrelevant.
|
||||
- `TabConfig` → opens the stored tab config. `NewSessionShell` irrelevant (config defines its own panes/commands).
|
||||
|
||||
"Make default" from the sidecar:
|
||||
- **Terminal item** → sets `DefaultSessionMode::Terminal`. Doesn't touch `NewSessionShell`.
|
||||
- **A specific shell** (Windows, e.g., PowerShell) → sets `DefaultSessionMode::Terminal` *and* updates `NewSessionShell` to that shell. Now Cmd+T → terminal → PowerShell.
|
||||
- **Agent / Cloud Oz** → sets `DefaultSessionMode` to `Agent` / `CloudAgent`. Doesn't touch `NewSessionShell`.
|
||||
- **A user tab config** → sets `DefaultSessionMode::TabConfig` + stores config path in `default_tab_config_path`.
|
||||
|
||||
Key invariant: `NewSessionShell` is *always* the authority for which shell a terminal uses. `DefaultSessionMode` never duplicates that.
|
||||
|
||||
### 3. Create action sidecar render function
|
||||
**New file:** `app/src/tab_configs/action_sidecar.rs`
|
||||
|
||||
A free function `render_action_sidecar()` (not a `View`) that returns `Box<dyn Element>`. Called directly from the Workspace render method. This is simpler than a View since the sidecar has no internal state — it just reads the current item and settings to produce an element tree.
|
||||
|
||||
The function takes a struct describing what to show:
|
||||
```rust
|
||||
enum SidecarItemKind {
|
||||
BuiltIn { name: String, default_mode: DefaultSessionMode, shell: Option<AvailableShell> },
|
||||
UserTabConfig { config: TabConfig },
|
||||
}
|
||||
```
|
||||
|
||||
Button clicks dispatch `WorkspaceAction` variants directly (`TabConfigSidecarMakeDefault`, `TabConfigSidecarEditConfig`, `TabConfigSidecarRemoveConfig`), which the Workspace handles inline in `handle_action`.
|
||||
|
||||
**File:** `app/src/tab_configs/mod.rs` — add `pub(crate) mod action_sidecar;`
|
||||
|
||||
### 4. Add `RemoveTabConfigConfirmationDialog`
|
||||
**New file:** `app/src/tab_configs/remove_confirmation_dialog.rs`
|
||||
|
||||
Follows the `CloseSessionConfirmationDialog` pattern (`app/src/workspace/close_session_confirmation_dialog.rs`):
|
||||
- Title: "Remove tab config?"
|
||||
- Body: "This will permanently delete {config_name} ({file_path})."
|
||||
- Buttons: Cancel / Remove (destructive)
|
||||
|
||||
Events: `RemoveTabConfigConfirmationEvent::Confirm { path } | Cancel`
|
||||
|
||||
On confirm, delete the file from disk. The filesystem watcher handles menu refresh. If the removed config was the default, clear `default_tab_config_path` and set `DefaultSessionMode` back to `Terminal`.
|
||||
|
||||
### 5. Wire up action sidecar in Workspace
|
||||
**File:** `app/src/workspace/view.rs`
|
||||
|
||||
Add new fields to `Workspace`:
|
||||
```rust
|
||||
tab_config_action_sidecar_item: Option<SidecarItemKind>,
|
||||
tab_config_action_sidecar_mouse_states: SidecarMouseStates,
|
||||
remove_tab_config_confirmation_dialog: ViewHandle<RemoveTabConfigConfirmationDialog>,
|
||||
```
|
||||
|
||||
The sidecar is shown when `tab_config_action_sidecar_item` is `Some`. No separate `bool` is needed.
|
||||
|
||||
**In `update_new_session_sidecar()`**: Extend the match to handle all actionable items (Terminal, shell variants, Agent, Cloud Oz, user tab configs). For these, set `tab_config_action_sidecar_item` to `Some(item_kind)`. For "New worktree config", keep existing behavior. For "New Tab Config" and separators, set it to `None`.
|
||||
|
||||
**In `render()`**: Add a second positioned overlay that calls `render_action_sidecar()` when `tab_config_action_sidecar_item` is `Some` (same positioning logic as the existing sidecar). This overlay uses the same `OffsetPositioning::offset_from_save_position_element` anchored to the hovered menu item label, so it works identically in both horizontal and vertical tabs modes.
|
||||
|
||||
**Event handlers**: `WorkspaceAction::TabConfigSidecar*` variants are handled directly in `handle_action`. Subscribe to `RemoveTabConfigConfirmationEvent`.
|
||||
|
||||
### 6. Rename `AddTab` → `AddDefaultTab` and route Cmd+T through it
|
||||
**Files:** `app/src/workspace/action.rs`, `app/src/workspace/view.rs`, `app/src/workspace/mod.rs`, `app/src/app_menus.rs`
|
||||
|
||||
Rename `WorkspaceAction::AddTab` to `WorkspaceAction::AddDefaultTab` to clearly distinguish it from the explicit `AddTerminalTab` and `AddAgentTab` actions:
|
||||
- `AddDefaultTab` = "open whatever the user's default is" (the Cmd+T action). Checks `DefaultSessionMode` and routes accordingly.
|
||||
- `AddTerminalTab` = "always open a terminal, ignoring default" (explicit override, has its own keybinding).
|
||||
- `AddAgentTab` = "always open an agent tab" (explicit override).
|
||||
|
||||
The `AddDefaultTab` handler checks the effective `DefaultSessionMode`:
|
||||
1. `TabConfig` → call `resolved_default_tab_config()`. If found, call `open_tab_config()`. If missing, clear to `Terminal` and fall through.
|
||||
2. `CloudAgent` → call `add_ambient_agent_tab()`.
|
||||
3. `Agent` / `Terminal` → existing behavior (`add_terminal_tab` internally respects Agent mode).
|
||||
|
||||
**macOS native menu (Cmd+T routing):**
|
||||
On macOS, Cmd+T is handled by the native menu system, not the WarpUI keybinding system. The native menu's "New Terminal Tab" item holds Cmd+T for non-Agent modes; "New Agent Tab" holds it for Agent mode. Both callbacks ultimately dispatch through `CustomAction::NewTab` → `AddDefaultTab`.
|
||||
|
||||
The callback `open_new_default_tab_or_window` (`app/src/app_menus.rs`) always dispatches `CustomAction::NewTab`, which the binding system maps to `WorkspaceAction::AddDefaultTab`. This means Cmd+T always goes through the `AddDefaultTab` handler regardless of the current mode — the handler is the single place that routes based on `DefaultSessionMode`.
|
||||
|
||||
### 7. Flatten Windows Terminal shell items
|
||||
**File:** `app/src/workspace/view.rs`
|
||||
|
||||
In `unified_new_session_menu_items()`, replace the `#[cfg(target_os = "windows")]` block (`view.rs:5191`) that creates a submenu parent with code that lists each `AvailableShell` as an individual top-level `MenuItem` with `AddTabWithShell` action.
|
||||
|
||||
Remove `NewSessionSidecarKind::Terminal`, `configure_terminal_new_session_sidecar()`, and related dead code.
|
||||
|
||||
### 8. Fix editor setting for tab config file opens
|
||||
**File:** `app/src/workspace/view.rs`
|
||||
|
||||
In `create_and_open_new_tab_config()` (`view.rs:5469`), change:
|
||||
```rust
|
||||
let target = resolve_file_target(&path, settings, None);
|
||||
```
|
||||
to:
|
||||
```rust
|
||||
let target = resolve_file_target_with_editor_choice(
|
||||
&path,
|
||||
*settings.open_code_panels_file_editor,
|
||||
*settings.prefer_markdown_viewer,
|
||||
*settings.open_file_layout,
|
||||
None,
|
||||
);
|
||||
```
|
||||
|
||||
Apply the same fix to `save_current_tab_as_new_config()` (`view.rs:5500`) and the `OpenTabConfigErrorFile` handler (`view.rs:18250`).
|
||||
|
||||
The "Edit config" button in the action sidecar will also use `resolve_file_target_with_editor_choice` with `open_code_panels_file_editor`.
|
||||
|
||||
### 9. Update settings dropdown
|
||||
**File:** `app/src/settings_view/features_page.rs`
|
||||
|
||||
Replace the `default_session_mode_dropdown: ViewHandle<Dropdown<FeaturesPageAction>>` (`features_page.rs:1222`) with a `FilterableDropdown<FeaturesPageAction>`. The `FilterableDropdown` component (`app/src/view_components/filterable_dropdown.rs`) already supports search/filter, arrow key navigation, and the same `DropdownItem` API — it wraps a `Menu` with a search editor, so users can type to narrow down the list when many tab configs are present.
|
||||
|
||||
In `update_default_session_mode_dropdown()` (`features_page.rs:3273`), after the `DefaultSessionMode::iter()` items (Terminal, Agent), append an item for each loaded tab config from `WarpConfig::tab_configs()`, using the config name as the display label and dispatching a new `FeaturesPageAction` variant that sets both `DefaultSessionMode::TabConfig` and `default_tab_config_path`.
|
||||
|
||||
Subscribe to `WarpConfigUpdateEvent::TabConfigs` to rebuild the dropdown when configs change.
|
||||
|
||||
### 10. Cmd+T keybinding indicator in menu
|
||||
**File:** `app/src/workspace/view.rs`
|
||||
|
||||
In `unified_new_session_menu_items()`, the Cmd+T shortcut label is currently assigned to either the Agent or Terminal item based on `default_is_agent`. Replace this with a comprehensive check of the effective default:
|
||||
|
||||
1. `DefaultSessionMode::TabConfig` → attach the shortcut label to the matching tab config's menu item (matched by `source_path`).
|
||||
2. `DefaultSessionMode::Agent` → attach to the Agent item (existing behavior).
|
||||
3. `DefaultSessionMode::CloudAgent` → attach to the Cloud Oz item.
|
||||
4. `DefaultSessionMode::Terminal` → attach to the "Terminal" item.
|
||||
|
||||
Note: Per-shell shortcut label logic (i.e., showing Cmd+T on the specific shell item when a shell is made default on Windows) is not implemented in v1. The shortcut label always appears on the "Terminal" item when mode is `Terminal`, regardless of which shell was selected via "Make default".
|
||||
|
||||
## End-to-end flow
|
||||
|
||||
### Hovering a tab config in the menu
|
||||
1. User hovers over a tab config item in the dropdown (works identically in horizontal and vertical tabs).
|
||||
2. `handle_new_session_menu_event` → `ItemHovered` → `update_new_session_sidecar()`.
|
||||
3. Match identifies the item as a user tab config (by checking if the action is `SelectTabConfig`).
|
||||
4. Populates `tab_config_action_sidecar` with the config's name, path, and all three buttons.
|
||||
5. Sets `show_tab_config_action_sidecar = true`, hides `show_new_session_sidecar`.
|
||||
6. Render places the sidecar overlay anchored to the hovered item.
|
||||
|
||||
### "Make default" for a tab config
|
||||
1. User clicks "Make default" in the action sidecar.
|
||||
2. Sidecar dispatches `WorkspaceAction::TabConfigSidecarMakeDefault { mode: TabConfig, tab_config_path: Some(path), shell: None }`.
|
||||
3. Workspace handler sets `default_session_mode_internal` to `TabConfig` and `default_tab_config_path` to the file path.
|
||||
4. Menu closes. Next time it opens, the Cmd+T shortcut label appears on that config's menu item.
|
||||
5. Settings dropdown updates via the `AISettingsChangedEvent` subscription.
|
||||
|
||||
### Cmd+T with tab config default
|
||||
1. User presses Cmd+T → native menu dispatches `CustomAction::NewTab` → `AddDefaultTab` action.
|
||||
2. Handler checks `DefaultSessionMode::TabConfig`.
|
||||
3. Reads `default_tab_config_path`, finds matching config in `WarpConfig::tab_configs()` via `resolved_default_tab_config()`.
|
||||
4. Calls `open_tab_config()` → shows params modal if needed, else opens directly.
|
||||
5. If config file is missing, clears settings to `Terminal` and opens a normal terminal tab.
|
||||
|
||||
### "Remove" flow
|
||||
1. User clicks "Remove" in the sidecar.
|
||||
2. Sidecar dispatches `WorkspaceAction::TabConfigSidecarRemoveConfig { name, path }`.
|
||||
3. Workspace opens `RemoveTabConfigConfirmationDialog` with the config name and path.
|
||||
4. User confirms → dialog emits `Confirm { path }`.
|
||||
5. Handler deletes the file. If removed config was the default, clears `default_tab_config_path` and sets mode to `Terminal`.
|
||||
6. Filesystem watcher reloads configs; menu updates on next open.
|
||||
|
||||
## Risks and mitigations
|
||||
|
||||
- **Backward compat for `DefaultSessionMode` serialization**: Adding a `TabConfig` variant changes serialized values. Old clients reading a `TabConfig` value will fail to deserialize and fall back to the default (`Terminal`). This is acceptable — the worst case is losing the default preference on downgrade.
|
||||
- **Race between file deletion and watcher**: After "Remove" deletes the file, there's a brief window where the config is still in `WarpConfig::tab_configs()`. The watcher debounce handles this. The sidecar closes the menu on removal, so the user won't see a stale entry.
|
||||
- **Large number of tab configs**: The settings dropdown and menu will list all configs. No pagination is needed for v1, but configs with identical names are disambiguated by file path in the sidecar subtitle.
|
||||
|
||||
## Testing and validation
|
||||
|
||||
- **Unit tests**:
|
||||
- `TabConfig.source_path` is populated after parsing.
|
||||
- `DefaultSessionMode::TabConfig` + `default_tab_config_path` round-trips through serialization.
|
||||
- `resolved_default_tab_config()` returns `None` when path is empty, missing, or not in loaded configs.
|
||||
- `resolve_file_target_with_editor_choice` is used with `open_code_panels_file_editor` for all tab config file opens.
|
||||
- **Integration / computer-use**:
|
||||
- **Both horizontal and vertical tabs**: Open the tab configs menu in each mode, hover items, verify sidecar appears and is positioned correctly.
|
||||
- Make default → verify Cmd+T behavior and settings dropdown sync.
|
||||
- Edit config → verify correct editor opens.
|
||||
- Remove → verify confirmation dialog, file deletion, menu update, and default fallback.
|
||||
- Keyboard navigation through menu → sidecar updates.
|
||||
- Vertical tabs: verify the action sidecar doesn't conflict with the vertical tabs detail sidecar (they are independent systems).
|
||||
- **Regression**:
|
||||
- "New worktree config" repo-list sidecar unchanged.
|
||||
- Existing `DefaultSessionMode::Terminal` and `Agent` behavior unchanged.
|
||||
- Vertical tabs detail sidecar (hover-to-preview pane details) unaffected.
|
||||
|
||||
## Follow-ups
|
||||
- Potential for tab config reordering in the menu.
|
||||
- Richer sidecar content (preview of pane layout, param summary).
|
||||
Reference in New Issue
Block a user