Add sidebar toggle, agent panel side, and workspace telemetry events

Katie Geer created

Track three new telemetry signals:

- 'Sidebar Toggled' fires in MultiWorkspace::open_sidebar and
  close_sidebar with action ('open'/'close') and side ('left'/'right').
  Covers both mouse (status bar button / sidebar close button) and
  keyboard (ToggleWorkspaceSidebar / CloseWorkspaceSidebar actions)
  since all paths funnel through those two methods.

- 'Agent Panel Side Changed' fires in AgentPanel::set_position with
  the new side value. This fires whenever the user moves the panel
  via drag or the context menu.

- 'Workspace Added' fires in MultiWorkspace::add — which is only called
  during initial workspace restoration, not for user-initiated project
  opens — with workspace_count so we can understand portfolio size at
  session start.

- 'Sidebar Open Project Clicked' fires on the empty-state 'Open Project'
  button in the sidebar with the current side value.

Change summary

Cargo.lock                              |  1 +
crates/agent_ui/src/agent_panel.rs      |  5 +++++
crates/sidebar/Cargo.toml               |  1 +
crates/sidebar/src/sidebar.rs           |  6 ++++++
crates/workspace/src/multi_workspace.rs | 14 ++++++++++++++
5 files changed, 27 insertions(+)

Detailed changes

Cargo.lock 🔗

@@ -16093,6 +16093,7 @@ dependencies = [
  "serde",
  "serde_json",
  "settings",
+ "telemetry",
  "theme",
  "theme_settings",
  "ui",

crates/agent_ui/src/agent_panel.rs 🔗

@@ -3344,6 +3344,11 @@ impl Panel for AgentPanel {
     }
 
     fn set_position(&mut self, position: DockPosition, _: &mut Window, cx: &mut Context<Self>) {
+        let side = match position {
+            DockPosition::Left => "left",
+            _ => "right",
+        };
+        telemetry::event!("Agent Panel Side Changed", side = side);
         settings::update_settings_file(self.fs.clone(), cx, move |settings, _| {
             settings
                 .agent

crates/sidebar/Cargo.toml 🔗

@@ -35,6 +35,7 @@ remote.workspace = true
 serde.workspace = true
 serde_json.workspace = true
 settings.workspace = true
+telemetry.workspace = true
 theme.workspace = true
 theme_settings.workspace = true
 ui.workspace = true

crates/sidebar/src/sidebar.rs 🔗

@@ -34,6 +34,7 @@ use settings::Settings as _;
 use std::collections::{HashMap, HashSet};
 use std::mem;
 use std::rc::Rc;
+use telemetry;
 use theme::ActiveTheme;
 use ui::{
     AgentThreadStatus, CommonAnimationExt, ContextMenu, Divider, HighlightedLabel, KeyBinding,
@@ -3521,6 +3522,11 @@ impl Sidebar {
                     .full_width()
                     .key_binding(KeyBinding::for_action(&workspace::Open::default(), cx))
                     .on_click(|_, window, cx| {
+                        let side = match AgentSettings::get_global(cx).sidebar_side() {
+                            SidebarSide::Left => "left",
+                            SidebarSide::Right => "right",
+                        };
+                        telemetry::event!("Sidebar Open Project Clicked", side = side);
                         window.dispatch_action(
                             Open {
                                 create_new_window: false,

crates/workspace/src/multi_workspace.rs 🔗

@@ -468,6 +468,11 @@ impl MultiWorkspace {
     }
 
     pub fn open_sidebar(&mut self, cx: &mut Context<Self>) {
+        let side = match self.sidebar_side(cx) {
+            SidebarSide::Left => "left",
+            SidebarSide::Right => "right",
+        };
+        telemetry::event!("Sidebar Toggled", action = "open", side = side);
         self.sidebar_open = true;
         if let ActiveWorkspace::Transient(workspace) = &self.active_workspace {
             let workspace = workspace.clone();
@@ -485,6 +490,11 @@ impl MultiWorkspace {
     }
 
     pub fn close_sidebar(&mut self, window: &mut Window, cx: &mut Context<Self>) {
+        let side = match self.sidebar_side(cx) {
+            SidebarSide::Left => "left",
+            SidebarSide::Right => "right",
+        };
+        telemetry::event!("Sidebar Toggled", action = "close", side = side);
         self.sidebar_open = false;
         for workspace in self.workspaces.iter() {
             workspace.update(cx, |workspace, _cx| {
@@ -861,7 +871,11 @@ impl MultiWorkspace {
     /// persistent list regardless of sidebar state — it's used for system-
     /// initiated additions like deserialization and worktree discovery.
     pub fn add(&mut self, workspace: Entity<Workspace>, window: &Window, cx: &mut Context<Self>) {
+        let is_new = !self.workspaces.iter().any(|w| *w == workspace);
         self.insert_workspace(workspace, window, cx);
+        if is_new {
+            telemetry::event!("Workspace Added", workspace_count = self.workspaces.len());
+        }
     }
 
     /// Ensures the workspace is in the multiworkspace and makes it the active one.