1# Dock Buttons Redesign: Grouped Sub-Panel Toggles
2
3## Goal
4
5Allow the agent thread view and the threads sidebar to be independently toggled from the status bar, while sharing a single dock slot and rendering side by side when both are active.
6
7Currently the threads sidebar and the thread view are combined as a single agent panel, so this the design in this doc leverages this existing state to move us to a world where we can control each sub-panel's visibility with independent dock buttons.
8
9Today, each panel in a dock gets exactly one icon button in the status bar, and only one panel can be visible per dock at a time. We want to support panels that expose **multiple buttons**, each controlling an independent sub-view within the panel.
10
11## Design
12
13The core of the idea add the methods to the `Panel` trait to let them return multiple buttons and have these be rendered in the status bar. We can then handle these pretty much uniformly for all panels, even though only the agent panel will have multiple dock buttons.
14
15### New `DockButton` struct
16
17A small struct describing a single button to render in the status bar:
18
19- `name: &'static str` — element ID for the button
20- `icon: IconName` — the icon to display
21- `tooltip: SharedString` — hover tooltip text
22- `action: Box<dyn Action>` — action dispatched on click
23- `is_active: bool` — whether the button appears toggled on
24
25### New `dock_buttons()` method on `Panel`
26
27```rust
28fn dock_buttons(&self, window: &Window, cx: &App) -> Vec<DockButton>
29```
30
31The default implementation delegates to the existing `icon()`, `icon_tooltip()`, and `toggle_action()` methods, returning a single `DockButton` with `is_active: true`. This means every existing panel works without changes.
32
33Panels that want multiple buttons (like `AgentPanel`) override this method and return one entry per sub-view, each with independent `is_active` state.
34
35The corresponding `PanelHandle` trait and its `Entity<T>` impl get a forwarding method.
36
37### `PanelButtons::render` changes
38
39Instead of creating one button per panel entry from `icon()`, the render method calls `dock_buttons()` on each panel and iterates over the results.
40
41For each button, the active state is `panel_is_active && dock_button.is_active`, where `panel_is_active` is the existing check (`Some(i) == active_panel_index && is_open`).
42
43When a panel produces more than one button, they are wrapped in a visual container (e.g., a bordered pill) to indicate grouping. When there is only one button, it renders the same as today.
44
45The right-click context menu ("Dock Left/Right/Bottom") appears on any button in the group but applies to the entire panel — the group moves together.
46
47### Click behavior
48
49Clicking a button always dispatches that button's `action`. The panel is responsible for handling the action and toggling the corresponding sub-view. If the panel is not already active in the dock, the click handler first activates the panel and opens the dock before dispatching the action.
50
51### Dock open/close lifecycle
52
53When the `AgentPanel` handles a button action and hides its last visible sub-view, it emits `PanelEvent::Close` to tell the dock to close. The dock's `is_open` field remains a stored bool managed externally — no derivation logic.
54
55### `AgentPanel` implementation
56
57`AgentPanel` overrides `dock_buttons()` to return two entries:
58
591. **Thread view** — icon for the agent, tooltip "Agent Panel", action `ToggleFocus`, `is_active` based on whether the thread view is showing
602. **Threads sidebar** — icon for the sidebar, tooltip "Threads Sidebar", action `ToggleWorkspaceSidebar`, `is_active` based on `sidebar.is_open()`
61
62The sidebar continues to live inside `AgentPanel`'s render tree, positioned to the outside of the thread view based on dock position (left or right).
63
64## Files to change
65
66- **`crates/workspace/src/dock.rs`** — Add `DockButton` struct, `dock_buttons()` to `Panel` + `PanelHandle` + `Entity<T>` impl, update `PanelButtons::render`
67- **`crates/agent_ui/src/agent_panel.rs`** — Override `dock_buttons()` on `AgentPanel`
68
69## Future consideration: derived `is_open`
70
71The current design keeps `is_open` as externally-managed state. Ideally, the dock's open/close state would be **derived** from whether any `dock_buttons()` report `is_active: true`. This would eliminate the `PanelEvent::Close` coordination and make the system more declarative.
72
73The challenge is that ~18 call sites in `workspace.rs` imperatively call `set_open()` — for keyboard shortcuts, serialization restore, zoom management, and programmatic panel control. These all assume `is_open` is a stored field they can set directly.
74
75For panels using the default `dock_buttons()` (which always returns `is_active: true`), fully derived state would mean the dock can never be closed, since the panel doesn't know about the dock's visibility.
76
77Unifying these two models — imperative open/close for simple panels and derived open/close for multi-button panels — needs more thought. Some directions to explore:
78
79- The default `dock_buttons()` impl could take the dock's current visibility as input and reflect it in `is_active`
80- `set_open` could be replaced with panel activation/deactivation at a higher level
81- A two-tier model where the dock auto-derives state only for panels that opt in
82
83For now, the `PanelEvent::Close` approach works and keeps the change small.
84
85## Non-goals
86
87- Arbitrary panel grouping (panels from different crates sharing a dock slot). This design is forward-compatible with a future `PanelGroup` container but does not implement it.
88- Sub-panel-specific context menu items. The right-click menu applies to the whole group.
89- Final visual design for the grouped buttons. We will do a simple implementation first and pair with design for polish.