Initial public release of Warp.
Repo-Sync-Origin: warpdotdev/warp-internal@12af1d983b
This commit is contained in:
@@ -0,0 +1,150 @@
|
||||
# Configurable Toolbar Chips (Header Toolbar)
|
||||
|
||||
**Linear:** [APP-3865](https://linear.app/warpdotdev/issue/APP-3865)
|
||||
**Figma:** none provided
|
||||
|
||||
## Summary
|
||||
|
||||
The app header bar shows a set of panel toggle buttons (tabs panel, tools panel, agent management, code review, notifications mailbox). Users should be able to add/remove, re-arrange, and move these items between the left and right sides of the header, using the same configurator UI pattern as the agent input footer chip editor. This applies to both horizontal and vertical tab modes.
|
||||
|
||||
## Problem
|
||||
|
||||
The toolbar layout is currently hardcoded, outside of customizing which items appear. Users cannot customize item order, or which side items are on. Users with different workflows may want different toolbar layouts — for example, a user who never uses code review shouldn't have to see that button, and a user who frequently uses agent management may want it on the right side near their cursor.
|
||||
|
||||
## Goals
|
||||
|
||||
- Let users reorder toolbar items within a side (left or right).
|
||||
- Let users move toolbar items between left and right sides.
|
||||
- Let users hide toolbar items they don't need.
|
||||
- Let users restore hidden toolbar items.
|
||||
- Integrate with the existing onboarding show/hide mechanism so that onboarding choices and configurator choices stay consistent.
|
||||
- Persist configuration across sessions via settings.
|
||||
|
||||
## Non-goals
|
||||
|
||||
- The search bar remains centered and is not configurable (vertical tabs mode only; not shown in horizontal tabs).
|
||||
- The avatar/profile button remains fixed at the far right and is not configurable.
|
||||
|
||||
## User experience
|
||||
|
||||
### Configurable items
|
||||
|
||||
The following toolbar items are configurable:
|
||||
|
||||
1. **Tabs panel** — toggles the vertical tabs sidebar (hamburger icon)
|
||||
2. **Tools panel** — toggles the left panel containing project explorer, global search, warp drive, conversation history (tool icon)
|
||||
3. **Agent management** — toggles the agent management view (grid icon)
|
||||
4. **Code review** — toggles the code review panel (right panel button)
|
||||
5. **Notifications mailbox** — toggles the notification mailbox (inbox icon)
|
||||
|
||||
Each item is gated on the same feature flags and settings that currently control its visibility (e.g. agent management requires AI enabled + `FeatureFlag::AgentManagementView`, notifications requires `FeatureFlag::HOANotifications`). Items whose prerequisites are not met do not appear in the toolbar or in the configurator.
|
||||
|
||||
### Default layout
|
||||
|
||||
The default layout matches the current hardcoded layout:
|
||||
- **Left:** Tabs panel, Tools panel, Agent management
|
||||
- **Right:** Code review, Notifications mailbox
|
||||
|
||||
### Side placement determines panel open side
|
||||
|
||||
Moving a toolbar item to the left or right side determines which side of the main content area its panel opens on:
|
||||
|
||||
- **Tabs panel:** Opens its sidebar on whichever side its button is placed. Not pinned to the left.
|
||||
- **Tools panel:** Opens on whichever side its button is placed (left or right of main content).
|
||||
- **Code review:** Opens on whichever side its button is placed.
|
||||
- **Agent management:** Replaces the main content area entirely — side placement only affects button position, not where it renders.
|
||||
- **Notifications mailbox:** The popover anchors to its button position — top-left corner under the button's bottom-left when on the left side, top-right under bottom-right when on the right. Notification toasts also appear on the same side as the mailbox button.
|
||||
|
||||
Within a side, panels open in the order their buttons appear in the toolbar. For example, if the left side is configured as `[Code Review, Tabs Panel, Tools Panel]`, then when all three are open, the panels render left-to-right as: Code Review | Tabs Panel | Tools Panel | Main Content.
|
||||
|
||||
### Side-aware UI element flipping
|
||||
|
||||
When a panel or its parent container moves to the opposite side, all associated UI elements flip to avoid rendering off-screen:
|
||||
|
||||
- **Tabs panel detail sidecar:** When the tabs panel is on the left, the hover-detail sidecar opens to the right. When on the right, it opens to the left.
|
||||
- **Conversation list tooltips:** Item hover tooltips open toward the center of the screen (right when panel is on the left, left when on the right).
|
||||
- **Conversation list kebab menu:** The overflow button and its opened menu anchor toward the center.
|
||||
- **Warp Drive item kebab button:** When the tools panel is on the right, the kebab `⋮` button renders as a left-aligned overlay (flush with the left edge) instead of appending to the right end of the row. The opened menu also opens toward the center.
|
||||
- **Warp Drive hover previews and dialogs:** Hover preview sidecars and share/naming dialogs flip to open toward the center.
|
||||
- **Vertical tabs action buttons:** The kebab/close button container appears on the opposite side of the tab group, and the right-click menu anchors accordingly.
|
||||
- **Panel borders and resize handles:** The drag bar and border line render on the edge facing the main content (right edge when panel is on the left, left edge when on the right).
|
||||
|
||||
### Configuration entry points
|
||||
|
||||
1. **Right-click context menu:** Right-clicking on any toolbar item or on the toolbar area between the left items and the search bar / between the search bar and the right items shows a context menu with an "Edit toolbar" option that opens the configurator modal.
|
||||
2. **Settings page:** The configurator is also accessible from the Settings page under Appearance > Tabs, using the same pattern as the agent input footer toolbelt editors (the "Edit agent toolbelt" / "Edit CLI agent toolbelt" buttons accessible under Appearance > Input type). A button like "Edit toolbar" opens the same configurator modal.
|
||||
|
||||
### Configurator modal
|
||||
|
||||
The modal uses the same drag-and-drop `ChipConfigurator` pattern as the agent input footer editor (`app/src/ai/blocklist/agent_view/agent_input_footer/editor.rs`):
|
||||
|
||||
- **Title:** "Edit toolbar"
|
||||
- **Available items** section: shows items not currently placed on either side. Chips can be dragged from here to either drop zone.
|
||||
- **Left side** drop zone: shows items that render on the left side of the header bar. Items can be reordered via drag-and-drop.
|
||||
- **Right side** drop zone: shows items that render on the right side of the header bar. Items can be reordered via drag-and-drop.
|
||||
- Each placed item has an X button to remove it (moves it back to available items).
|
||||
- Items can be dragged between zones (left → right, right → available, available → left, etc.).
|
||||
- **Restore default** link: resets to the default layout. Disabled when already at defaults.
|
||||
- **Cancel** button: discards changes and closes.
|
||||
- **Save changes** button: persists the configuration and closes. Disabled when no changes have been made.
|
||||
|
||||
### Interaction with existing show/hide settings
|
||||
|
||||
The existing settings that control item visibility (`TabSettings::show_code_review_button`, `AISettings::show_agent_notifications`, etc.) remain the source of truth for whether an item is shown. When a user removes an item from the toolbar via the configurator, the corresponding existing setting is toggled off. When a user adds an item back, the setting is toggled on.
|
||||
|
||||
This ensures that:
|
||||
- Onboarding choices are respected: if onboarding hides code review, it won't appear in the toolbar or in the configurator's "available" section until the prerequisite is met.
|
||||
- Settings page toggles remain consistent with the toolbar configuration.
|
||||
|
||||
### Ordering and side placement persistence
|
||||
|
||||
A new setting stores the user's custom ordering and side placement. The setting stores a list of item identifiers for the left side and right side. When the setting is `Default`, the hardcoded default layout is used. When `Custom`, the stored lists determine order and placement.
|
||||
|
||||
Items that are "available" (prerequisites met + existing show/hide setting is on) but not present in either the left or right list are treated as hidden by the user.
|
||||
|
||||
### Edge cases
|
||||
|
||||
- If an item's prerequisite becomes unmet after configuration (e.g. AI is disabled, removing agent management), that item silently disappears from the toolbar. It remains in the stored configuration so it reappears if the prerequisite is met again.
|
||||
- If all items are removed from both sides, the toolbar area remains but is empty (just spacing).
|
||||
- If a panel is open and the user reconfigures its button to the opposite side, the panel should close and reopen on the new side on next toggle.
|
||||
|
||||
## Success criteria
|
||||
|
||||
1. Right-clicking the toolbar area (in both horizontal and vertical tabs mode) opens a context menu with "Edit toolbar".
|
||||
2. The configurator modal opens and displays the correct current layout.
|
||||
3. Items can be dragged between left, right, and available zones, and reordered within zones.
|
||||
4. Clicking X on a placed item moves it to available.
|
||||
5. Saving persists the configuration; reopening the configurator shows the saved layout.
|
||||
6. The toolbar renders items in the configured order and sides.
|
||||
7. "Restore default" resets to the hardcoded default layout.
|
||||
8. Removing an item from the toolbar via the configurator also toggles off the corresponding existing setting (e.g. `show_code_review_button` becomes false).
|
||||
9. Adding an item back toggles the setting on.
|
||||
10. Onboarding settings are respected: items hidden during onboarding are not shown until re-enabled.
|
||||
11. Items gated behind feature flags that are not enabled do not appear in the toolbar or configurator.
|
||||
12. Configuration survives app restart.
|
||||
13. Moving code review to the left side causes the code review panel to open on the left.
|
||||
14. Moving tools panel to the right side causes the tools panel to open on the right.
|
||||
15. Moving tabs panel to the right side causes the vertical tabs sidebar to open on the right.
|
||||
16. Panel ordering within a side is respected (if tabs panel is to the right of tools panel on the left side, the tabs sidebar renders to the right of the tools panel).
|
||||
17. Notification toasts appear on the same side as the mailbox button.
|
||||
18. Warp Drive item hover previews open toward the center of the screen (left when panel is on the right).
|
||||
19. Conversation list tooltips and kebab menus open toward the center.
|
||||
20. Vertical tabs detail sidecar opens toward the center.
|
||||
21. Warp Drive kebab button appears as a left-edge overlay when the panel is on the right side.
|
||||
|
||||
## Validation
|
||||
|
||||
- Manual testing: open the configurator, rearrange items, save, verify the toolbar updates. Restart app and verify persistence.
|
||||
- Verify onboarding integration: complete onboarding with code review disabled, then open the configurator and confirm code review is not in the "available" section until re-enabled in settings.
|
||||
- Verify feature flag gating: with `HOANotifications` disabled, confirm notifications mailbox does not appear in toolbar or configurator.
|
||||
- Verify panel side placement: move code review to the left, verify it opens on the left side of the main content.
|
||||
- Verify panel ordering: configure left as `[Code Review, Tabs Panel]`, open both, verify code review renders to the left of tabs panel.
|
||||
- Unit tests for the new setting type (serialization, default resolution, migration).
|
||||
|
||||
## Feature flag
|
||||
|
||||
The entire feature is gated behind `FeatureFlag::ConfigurableToolbar`, which is enabled by default in all release builds. When disabled, the toolbar renders the default hardcoded layout and the right-click context menu / settings entry point are hidden.
|
||||
|
||||
## Open questions
|
||||
|
||||
None — all questions have been resolved.
|
||||
@@ -0,0 +1,322 @@
|
||||
# TECH.md — Configurable Toolbar Chips (Header Toolbar)
|
||||
|
||||
**Product spec:** `specs/harryalbert/APP-3865/PRODUCT.md`
|
||||
|
||||
## Problem
|
||||
|
||||
The header bar (in both horizontal and vertical tabs mode) renders panel toggle buttons in a hardcoded order and side assignment. We need to make these configurable (reorderable, moveable between sides, hideable) using the same `ChipConfigurator` pattern already used for the agent input footer. Critically, the side a button is placed on determines which side its panel opens on and the order determines the panel stacking order.
|
||||
|
||||
The entire feature is gated behind `FeatureFlag::ConfigurableToolbar` (enabled by default in all release builds).
|
||||
|
||||
## Relevant code
|
||||
|
||||
### Panel rendering (two-layer system that needs unification)
|
||||
- `app/src/workspace/view.rs:16993` — `render_panels`: outer layer composing `VTabs | theme chooser | Main | right panels`.
|
||||
- `app/src/workspace/view.rs:16545` — `render_banner_and_active_tab`: inner layer composing `LeftPanel(tools) | Terminal | RightPanel(code review)`.
|
||||
- `app/src/workspace/view/vertical_tabs.rs:1120` — `render_vertical_tabs_panel`: renders the vertical tabs sidebar with a `Resizable` wrapper and `DragBarSide::Right`.
|
||||
|
||||
### Panel toggle state
|
||||
- `app/src/pane_group/mod.rs:843` — `PaneGroup::left_panel_open: bool` (tools panel)
|
||||
- `app/src/pane_group/mod.rs:845` — `PaneGroup::right_panel_open: bool` (code review)
|
||||
- `app/src/workspace/view.rs` — `Workspace::vertical_tabs_panel_open: bool`
|
||||
- `app/src/workspace/view.rs` — `WorkspaceState::is_agent_management_view_open: bool` (replaces main content)
|
||||
- `app/src/workspace/view.rs` — `WorkspaceState::is_notification_mailbox_open: bool` (popover)
|
||||
|
||||
### Panel toggle logic
|
||||
- `app/src/workspace/view.rs:6711` — `toggle_left_panel`: tools panel open/close
|
||||
- `app/src/workspace/view.rs:6947` — `toggle_right_panel`: code review open/close
|
||||
- `app/src/workspace/view.rs:6669` — `toggle_vertical_tabs_panel`
|
||||
- `app/src/workspace/view.rs:18256` — `ToggleNotificationMailbox` handler
|
||||
|
||||
### Notification toast positioning
|
||||
- `app/src/workspace/view.rs:20265` — mailbox popover anchors to `NOTIFICATIONS_MAILBOX_POSITION_ID` (follows button naturally).
|
||||
- `app/src/workspace/view.rs:20342` — notification toasts anchor to `TAB_BAR_POSITION_ID` at `BottomRight`. This needs to become dynamic.
|
||||
|
||||
### Toolbar button rendering (current hardcoded layout)
|
||||
- `app/src/workspace/view.rs:15417` — `render_tab_bar_contents`: left-side buttons at 15522–15535, right-side via `add_right_side_tab_bar_controls` at 15543.
|
||||
- `app/src/workspace/view.rs:15639` — `add_right_side_tab_bar_controls`
|
||||
|
||||
### Existing chip configurator infrastructure
|
||||
- `app/src/chip_configurator/mod.rs` — `ChipConfigurator` with `LeftRightZones` layout.
|
||||
- `app/src/ai/blocklist/agent_view/agent_input_footer/editor.rs` — `AgentToolbarEditorModal` (template for our modal).
|
||||
|
||||
### Existing footer chip selection settings (pattern to follow)
|
||||
- `app/src/terminal/session_settings.rs:82` — `ToolbarChipSelection` trait, `AgentToolbarChipSelection` enum.
|
||||
- `app/src/terminal/session_settings.rs:277` — `agent_footer_chip_selection` setting.
|
||||
|
||||
### Onboarding
|
||||
- `app/src/settings/onboarding.rs:62` — `apply_ui_customization_settings`
|
||||
|
||||
## Current state
|
||||
|
||||
### Panel rendering: two-layer system
|
||||
|
||||
The workspace renders panels in two separate composition steps:
|
||||
|
||||
**Outer layer** (`render_panels`, line 16993):
|
||||
```
|
||||
Flex::row: [ VTabs panel | theme chooser(left) | Main content | resource center/AI assistant(right) ]
|
||||
```
|
||||
|
||||
**Inner layer** (`render_banner_and_active_tab`, line 16545):
|
||||
```
|
||||
Flex::row: [ LeftPanelView(tools) | terminal_content | RightPanelView(code review) ]
|
||||
```
|
||||
|
||||
The vertical tabs panel sits OUTSIDE the left/right panel pair, at a different level. Tools panel and code review are inside `render_banner_and_active_tab`. This two-layer split exists for a reason:
|
||||
|
||||
**Per-tab vs workspace-level state:** The inner layer panels (tools, code review) have **per-tab** open state — `PaneGroup::left_panel_open` and `PaneGroup::right_panel_open` are on `PaneGroup`, and each tab has its own `PaneGroup`. The tools panel can be open in tab 1 and closed in tab 2. The outer layer panels (vertical tabs, theme chooser, resource center) have **workspace-level** state that persists across tab switches.
|
||||
|
||||
To support arbitrary ordering, we need all configurable panels at the same composable level while preserving the per-tab open state for tools panel and code review. The `WorkspaceState::is_left_panel_open()` method (`workspace/util.rs:206`) only checks theme chooser state; `is_right_panel_open()` (`util.rs:202`) only checks resource center / AI assistant. These are workspace-global concerns separate from the configurable toolbar panels.
|
||||
|
||||
### Panel types and their current rendering
|
||||
|
||||
Each panel type has distinct rendering and state management:
|
||||
|
||||
- **Vertical tabs** (`render_vertical_tabs_panel`): `Resizable` wrapper with `DragBarSide::Right`. Self-contained rendering in `vertical_tabs.rs`. Open state: `Workspace::vertical_tabs_panel_open`.
|
||||
- **Tools panel** (`LeftPanelView`): rendered via `ChildView::new(&self.left_panel_view)`. Has its own `Resizable` wrapper inside `LeftPanelView`. Open state: `PaneGroup::left_panel_open`.
|
||||
- **Code review** (`RightPanelView`): rendered via `ChildView::new(&self.right_panel_view)`. Has its own `Resizable` wrapper. Open state: `PaneGroup::right_panel_open`. Can be maximized.
|
||||
- **Agent management**: replaces the terminal content area entirely (`ChildView::new(&self.agent_management_view)` at line 16555). Not a side panel.
|
||||
- **Notifications mailbox**: popover overlay anchored to `NOTIFICATIONS_MAILBOX_POSITION_ID`. Not a side panel.
|
||||
|
||||
### Toolbar button rendering
|
||||
|
||||
In the vertical tabs branch of `render_tab_bar_contents` (line 15522):
|
||||
- Left side: tabs panel toggle, tools panel toggle, agent management toggle
|
||||
- Right side (via `add_right_side_tab_bar_controls`): code review toggle, notifications mailbox, avatar
|
||||
|
||||
These are hardcoded in order and side.
|
||||
|
||||
## Proposed changes
|
||||
|
||||
### 1. New `HeaderToolbarItemKind` enum
|
||||
|
||||
New file: `app/src/workspace/header_toolbar_item.rs`
|
||||
|
||||
```rust
|
||||
#[derive(Clone, Debug, Eq, PartialEq, Hash, Serialize, Deserialize)]
|
||||
pub enum HeaderToolbarItemKind {
|
||||
TabsPanel,
|
||||
ToolsPanel,
|
||||
AgentManagement,
|
||||
CodeReview,
|
||||
NotificationsMailbox,
|
||||
}
|
||||
```
|
||||
|
||||
Methods:
|
||||
- `display_label(&self) -> &str` — human-readable name for the configurator.
|
||||
- `icon(&self) -> Icon` — icon for the configurator chip.
|
||||
- `is_available(app: &AppContext) -> bool` — checks feature flags and prerequisite settings.
|
||||
- `default_left() -> Vec<Self>` — `[TabsPanel, ToolsPanel, AgentManagement]`.
|
||||
- `default_right() -> Vec<Self>` — `[CodeReview, NotificationsMailbox]`.
|
||||
- `all_items() -> Vec<Self>` — all variants (availability filtering done at call site).
|
||||
- `is_panel(&self) -> bool` — true for items that open a side panel (TabsPanel, ToolsPanel, CodeReview). False for AgentManagement (replaces content) and NotificationsMailbox (popover).
|
||||
|
||||
### 2. New setting: `HeaderToolbarChipSelection`
|
||||
|
||||
In `app/src/workspace/tab_settings.rs`:
|
||||
|
||||
```rust
|
||||
#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq)]
|
||||
pub enum HeaderToolbarChipSelection {
|
||||
#[default]
|
||||
Default,
|
||||
Custom {
|
||||
left: Vec<HeaderToolbarItemKind>,
|
||||
right: Vec<HeaderToolbarItemKind>,
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
With `left_items()`, `right_items()` methods that resolve `Default` to hardcoded defaults. Add to `TabSettings` under `appearance.tabs` hierarchy.
|
||||
|
||||
### 3. Flatten the panel rendering into a single composition step
|
||||
|
||||
This is the core architectural change. Currently panels render at two levels. We unify them.
|
||||
|
||||
**Replace the two-layer system with a single `render_configurable_panels` method:**
|
||||
|
||||
In `render_banner_and_active_tab` (or a new unified method), build the panel row dynamically:
|
||||
|
||||
```
|
||||
let config = resolved toolbar config;
|
||||
let left_items = config.left_items filtered by is_available;
|
||||
let right_items = config.right_items filtered by is_available;
|
||||
|
||||
Flex::row:
|
||||
for item in left_items where item.is_panel() && item.is_open():
|
||||
render_panel_for_item(item, PanelPosition::Left)
|
||||
Main content (terminal or agent management)
|
||||
for item in right_items where item.is_panel() && item.is_open():
|
||||
render_panel_for_item(item, PanelPosition::Right)
|
||||
```
|
||||
|
||||
Where `render_panel_for_item` dispatches to:
|
||||
- `TabsPanel` → `render_vertical_tabs_panel` (with `DragBarSide` flipped if on right)
|
||||
- `ToolsPanel` → `ChildView::new(&self.left_panel_view)` (with drag bar side adjusted)
|
||||
- `CodeReview` → `ChildView::new(&self.right_panel_view)` (with drag bar side adjusted)
|
||||
|
||||
This moves vertical tabs out of `render_panels` and tools/code review out of `render_banner_and_active_tab`, into a unified composition.
|
||||
|
||||
**Preserving per-tab state:** The unified rendering must still read tools panel open state from `PaneGroup::left_panel_open` and code review from `PaneGroup::right_panel_open` (per-tab), while reading vertical tabs from `Workspace::vertical_tabs_panel_open` (workspace-level). The rendering loop checks the appropriate open state per item type, regardless of which side it's configured on.
|
||||
|
||||
**Drag bar side:** When a panel is on the left, its drag bar (resize handle) should be on the right (`DragBarSide::Right`). When on the right, it should be on the left (`DragBarSide::Left`). The vertical tabs panel currently hardcodes `DragBarSide::Right` (`vertical_tabs.rs:1174`). Tools panel and code review panel handle this internally in their respective views — these will need a `set_panel_position` method or constructor parameter.
|
||||
|
||||
### 4. Adapt panel toggle logic to be side-aware
|
||||
|
||||
Currently `toggle_left_panel` and `toggle_right_panel` are tightly coupled to the left/right concept. We need toggle methods that are panel-type-aware rather than side-aware.
|
||||
|
||||
Add a method that resolves which side a panel is on from the config:
|
||||
|
||||
```rust
|
||||
fn panel_side(&self, item: HeaderToolbarItemKind, app: &AppContext) -> Option<PanelPosition> {
|
||||
let config = TabSettings::as_ref(app).header_toolbar_chip_selection.value();
|
||||
if config.left_items().contains(&item) { Some(PanelPosition::Left) }
|
||||
else if config.right_items().contains(&item) { Some(PanelPosition::Right) }
|
||||
else { None }
|
||||
}
|
||||
```
|
||||
|
||||
The existing `ToggleLeftPanel` / `ToggleRightPanel` workspace actions continue to work — they just consult the config to determine which side the panel actually renders on. The `PaneGroup::left_panel_open` / `right_panel_open` booleans track open state independently of side placement.
|
||||
|
||||
### 5. Notification toast positioning
|
||||
|
||||
Currently notification toasts anchor at `PositionedElementAnchor::BottomRight` of `TAB_BAR_POSITION_ID` (`view.rs:20350`). When the mailbox button is on the left, this should become `BottomLeft`.
|
||||
|
||||
In the toast rendering code, read the toolbar config to determine which side the mailbox is on, and adjust the anchor accordingly.
|
||||
|
||||
### 6. Shared chip-editor modal rendering + `HeaderToolbarEditorModal`
|
||||
|
||||
The `AgentToolbarEditorModal` (`agent_input_footer/editor.rs`) and the new `HeaderToolbarEditorModal` (`workspace/header_toolbar_editor.rs`) render an identical modal UI (title, chip sections with left/right drop zones, restore-default link, cancel/save buttons, blur overlay). The duplication is purely in rendering — the domain logic (which settings to read/write, action types, event types) is genuinely different.
|
||||
|
||||
New file `app/src/chip_configurator/modal_shell.rs` provides:
|
||||
- `ChipEditorModalConfig<A>` — struct carrying everything that varies: title, available-section label, `is_at_defaults`, `is_dirty`, action values (cancel, save, reset, activate), `chip_action_wrapper`, and three `MouseStateHandle`s.
|
||||
- `render_chip_editor_modal<A>(configurator, config, appearance) -> Box<dyn Element>` — free function that renders the full modal (blur overlay, centered card, title, chip sections, buttons). Generic over the action type `A`.
|
||||
|
||||
Each modal view keeps its own `ChipConfigurator`, `MouseStateHandle`s, `is_dirty`, and domain state. Their `View::render` builds a `ChipEditorModalConfig` and calls `render_chip_editor_modal`. Their `TypedActionView::handle_action` handles save/open/reset with domain-specific logic.
|
||||
|
||||
**`HeaderToolbarEditorModal`** specifics:
|
||||
- On `open()`: reads `HeaderToolbarChipSelection` from `TabSettings`, builds `ConfigurableItem` list via `ControlItemRenderer::new_with_label_and_icon`.
|
||||
- On `save()`: writes back to `TabSettings::header_toolbar_chip_selection`. Syncs `show_code_review_button` and `show_agent_notifications`.
|
||||
- On `reset_default()`: restores default layout.
|
||||
|
||||
### 7. Integrate modal into Workspace
|
||||
|
||||
- Add `header_toolbar_editor_modal: ViewHandle<HeaderToolbarEditorModal>` field.
|
||||
- Add `WorkspaceAction::OpenHeaderToolbarEditor`.
|
||||
- Add `is_header_toolbar_editor_open` to `WorkspaceState`.
|
||||
- Render in the overlay stack.
|
||||
|
||||
### 8. Refactor `render_tab_bar_contents` to read from settings
|
||||
|
||||
Replace hardcoded button rendering in the vertical tabs branch with a loop:
|
||||
|
||||
```rust
|
||||
let config = TabSettings::as_ref(ctx).header_toolbar_chip_selection.value();
|
||||
for item in config.resolved_left(ctx) {
|
||||
tab_bar.add_child(self.render_header_toolbar_button(item, appearance, ctx));
|
||||
}
|
||||
// ... search bar ...
|
||||
for item in config.resolved_right(ctx) {
|
||||
right_controls.add_child(self.render_header_toolbar_button(item, appearance, ctx));
|
||||
}
|
||||
```
|
||||
|
||||
`render_header_toolbar_button` dispatches to existing per-item render methods.
|
||||
|
||||
### 9. Right-click context menu
|
||||
|
||||
Wrap toolbar items/area with an `EventHandler` that intercepts right-click and opens a `Menu` with a single "Edit toolbar" entry dispatching `WorkspaceAction::OpenHeaderToolbarEditor`.
|
||||
|
||||
### 10. Settings page entry point
|
||||
|
||||
Add an "Edit toolbar" button widget in `appearance_page.rs` under the "Tabs" category, dispatching `WorkspaceAction::OpenHeaderToolbarEditor`.
|
||||
|
||||
### 11. Show/hide setting sync on save
|
||||
|
||||
When saving:
|
||||
- `CodeReview` → sync `TabSettings::show_code_review_button`
|
||||
- `NotificationsMailbox` → sync `AISettings::show_agent_notifications`
|
||||
- `ToolsPanel` → composite (no single toggle to sync)
|
||||
- `TabsPanel`, `AgentManagement` → no existing individual show/hide setting
|
||||
|
||||
### 12. Side-aware UI element flipping
|
||||
|
||||
When panels move sides, all overlay/popup UI elements associated with them must flip to point toward the center of the screen. This is implemented via helpers that read `HeaderToolbarChipSelection` from `TabSettings` at render time.
|
||||
|
||||
**`tools_panel_menu_direction(app) -> MenuDirection`** (`drive/items/item.rs`): Returns `MenuDirection::Right` when the tools panel is on the left, `MenuDirection::Left` when on the right. Used by Warp Drive items, conversation list items, and the sorting button.
|
||||
|
||||
**`tabs_panel_side(app) -> PanelPosition`** (`workspace/view.rs`): Returns the side the tabs panel button is on. Used by the detail sidecar, action buttons, and right-click menu.
|
||||
|
||||
**`is_mailbox_on_left(app) -> bool`** (`workspace/view.rs`): Returns whether the mailbox button is configured on the left. Used for mailbox popover and toast anchoring.
|
||||
|
||||
Flipped elements:
|
||||
- **Notification mailbox popover** (`view.rs`): Anchors `BottomLeft`/`TopLeft` when on left, `BottomRight`/`TopRight` when on right.
|
||||
- **Notification toasts** (`view.rs`): Same side-aware anchoring.
|
||||
- **Tabs panel detail sidecar** (`vertical_tabs.rs`): `detail_sidecar_offset_and_max_height` accepts `PanelPosition` and flips `TopRight→TopLeft` / `BottomRight→BottomLeft` (and negates the horizontal gap) when on the right.
|
||||
- **Vertical tabs action buttons** (`vertical_tabs.rs`): Overlay position flips from `TopRight` to `TopLeft`.
|
||||
- **Vertical tabs right-click menu** (`view.rs`): Anchors flip from `BottomLeft`/`TopLeft` to `BottomRight`/`TopRight`.
|
||||
- **Conversation list tooltips** (`conversation_list/item.rs`): `tooltip_opens_right` field on `ItemProps` flips `MiddleRight→MiddleLeft`.
|
||||
- **Conversation list kebab button + menu** (`conversation_list/item.rs`): Button position flips from `TopRight` to `TopLeft`; `MenuDirection` flips.
|
||||
- **Warp Drive item overflow button** (`drive/items/item.rs`): When `overflow_on_left`, the button renders as a `Stack` overlay at `MiddleLeft` (flush with edge) instead of appending to the flex row. This avoids pushing item content.
|
||||
- **Warp Drive hover previews and dialogs** (`drive/index.rs`): `add_row_overlay_to_stack` flips the X-axis anchor pair from `(Right, Left)` to `(Left, Right)` and negates the pixel offset.
|
||||
- **Panel borders** (`left_panel.rs`, `right_panel.rs`): Border side and `DragBarSide` are driven by `self.panel_position`.
|
||||
- **Vertical tabs panel** (`vertical_tabs.rs`): Border and drag bar side are driven by the `side` parameter.
|
||||
|
||||
## End-to-end flow
|
||||
|
||||
**Toolbar rendering:**
|
||||
1. `render_tab_bar_contents` reads `HeaderToolbarChipSelection` from `TabSettings`.
|
||||
2. For each configured left item (filtered by `is_available`), renders the corresponding button.
|
||||
3. Search bar renders in the middle.
|
||||
4. For each configured right item, renders the corresponding button.
|
||||
|
||||
**Panel rendering:**
|
||||
1. Unified panel composition reads the same config.
|
||||
2. For each left panel item that is open, renders the panel on the left of main content, in order.
|
||||
3. Main content (terminal or agent management) renders in the middle.
|
||||
4. For each right panel item that is open, renders the panel on the right, in order.
|
||||
|
||||
**Configuration flow:**
|
||||
1. User right-clicks toolbar → context menu → "Edit toolbar".
|
||||
2. `WorkspaceAction::OpenHeaderToolbarEditor` opens the modal.
|
||||
3. Modal reads current config, populates `ChipConfigurator`.
|
||||
4. User drags items, clicks save.
|
||||
5. Modal writes `Custom { left, right }` to `TabSettings::header_toolbar_chip_selection`.
|
||||
6. Toolbar and panels re-render from the new config.
|
||||
|
||||
## Risks and mitigations
|
||||
|
||||
**Risk:** Flattening the two-layer panel system is a significant refactor that touches the core workspace layout.
|
||||
**Mitigation:** Both the vertical and horizontal tab paths use the same config-driven rendering. The default config produces identical behavior to the previous hardcoded layout, so existing users see no change unless they customize.
|
||||
|
||||
**Risk:** Resizable panel drag bars need to flip direction based on side.
|
||||
**Mitigation:** The `Resizable` element already accepts `DragBarSide`. Vertical tabs hardcodes `Right` at `vertical_tabs.rs:1174`; tools and code review panels handle it internally. We pass the side through.
|
||||
|
||||
**Risk:** `PaneGroup::left_panel_open` / `right_panel_open` naming becomes confusing when panels can be on either side.
|
||||
**Mitigation:** Keep the field names as-is (they track the tools panel and code review open state respectively, and are per-tab). Rename can be a follow-up. The side a panel renders on is determined by the toolbar config, not by which boolean tracks its state.
|
||||
|
||||
**Risk:** Flattening the panel layers might break the per-tab vs workspace-level open state distinction.
|
||||
**Mitigation:** The unified rendering explicitly reads per-tab state (`PaneGroup::left_panel_open` / `right_panel_open`) for tools and code review, and workspace-level state (`Workspace::vertical_tabs_panel_open`) for the tabs panel. The existing `WorkspaceState::is_left_panel_open()` / `is_right_panel_open()` methods (which only concern theme chooser and resource center) remain unchanged since those panels are not configurable.
|
||||
|
||||
**Risk:** Show/hide setting desync if user toggles settings directly outside the configurator.
|
||||
**Mitigation:** Toolbar rendering always filters by `is_available()`. The configurator's `open()` re-evaluates availability each time.
|
||||
|
||||
## Testing and validation
|
||||
|
||||
- **Unit tests:** `HeaderToolbarChipSelection` serialization, resolution, availability filtering.
|
||||
- **Manual testing:**
|
||||
- Move code review to left → verify it opens left of main content.
|
||||
- Move tabs panel to right → verify sidebar opens right of main content.
|
||||
- Configure left as `[CodeReview, TabsPanel]`, open both → verify code review renders left of tabs.
|
||||
- Move notifications to left → verify toasts appear on left side.
|
||||
- Save config, restart, verify persistence.
|
||||
|
||||
## Follow-ups
|
||||
|
||||
- Rename `PaneGroup::left_panel_open` / `right_panel_open` to panel-type-specific names (e.g. `tools_panel_open`, `code_review_panel_open`).
|
||||
- Consider a dedicated `show_agent_management` boolean setting.
|
||||
- Flip additional secondary elements when on the right side: Warp Drive sorting button menu, create-new button menu, section header dialog positioning.
|
||||
- Vertical tabs settings popup positioning when the tabs panel is on the right side.
|
||||
- New-session dropdown menu and sidecar positioning when the tabs panel is on the right side.
|
||||
Reference in New Issue
Block a user