From 186ac22e29e5fc3c8b1f729e176bfdd4058ffb8b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miko=C5=82aj=20Kondratek?= <19799111+mkondratek@users.noreply.github.com> Date: Wed, 16 Sep 2026 09:25:15 +0200 Subject: [PATCH] telemetry: fire ui.panel_opened from initializeWebview on every host (#14101) * telemetry: fire ui.panel_opened from initializeWebview on every host ui.panel_opened was only captured from VscodeWebviewProvider, so the JetBrains plugin never emitted it: the activation funnel there jumps from user.extension_activated (core spawned on project open) straight to task events, with no signal that the Cline UI was ever rendered. The webview calls the initializeWebview RPC once on mount on every host, so capture the same event there with source "webview_initialized". Existing dashboards keyed on ui.panel_opened pick up JetBrains for free; VS Code gains one extra source value on an event it already emits. * telemetry: type the ui.panel_opened source A PanelOpenedSource union on capturePanelOpened keeps the documented source list and the call sites from drifting apart. Also note that on JetBrains webview_initialized fires on every webview reload, so a crash-restart loop emits one per restart. --- .../src/core/controller/ui/initializeWebview.ts | 5 +++++ .../src/services/telemetry/TelemetryService.ts | 13 +++++++++++-- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/apps/vscode/src/core/controller/ui/initializeWebview.ts b/apps/vscode/src/core/controller/ui/initializeWebview.ts index f095f29f90..7fd665f3f3 100644 --- a/apps/vscode/src/core/controller/ui/initializeWebview.ts +++ b/apps/vscode/src/core/controller/ui/initializeWebview.ts @@ -20,6 +20,11 @@ import { refreshOpenRouterModels } from "../models/refreshOpenRouterModels" */ export async function initializeWebview(controller: Controller, _request: EmptyRequest): Promise { try { + // The webview calls this once on mount, on every host. The VS Code-only + // "sidebar_resolved"/"sidebar_visible" sources never fire for JetBrains, so + // this is the only host-neutral signal that the Cline UI actually rendered. + telemetryService.capturePanelOpened("webview_initialized") + // Sync workflow toggles with the files on disk so the chat input's slash // command menu knows about workflows without requiring the user to open // the Workflows modal first (which is the only other place that refreshes diff --git a/apps/vscode/src/services/telemetry/TelemetryService.ts b/apps/vscode/src/services/telemetry/TelemetryService.ts index 21d51738df..222416a4f7 100644 --- a/apps/vscode/src/services/telemetry/TelemetryService.ts +++ b/apps/vscode/src/services/telemetry/TelemetryService.ts @@ -92,6 +92,13 @@ export enum TerminalHangStage { BUFFER_STUCK = "buffer_stuck", } +/** + * Which hook fired `ui.panel_opened`. "sidebar_resolved" / "sidebar_visible" come from + * VscodeWebviewProvider and are VS Code-only; "webview_initialized" comes from the webview's + * mount-time initializeWebview RPC and fires on every host. + */ +export type PanelOpenedSource = "sidebar_resolved" | "sidebar_visible" | "webview_initialized" + export type TelemetryMetadata = { /** * The extension or cline-core version. JetBrains and CLI have different @@ -328,7 +335,9 @@ export class TelemetryService { MODEL_FAVORITE_TOGGLED: "ui.model_favorite_toggled", // Tracks when a button is clicked BUTTON_CLICKED: "ui.button_clicked", - // Tracks when the Cline panel becomes visible + // Tracks when the Cline panel becomes visible; `source` (PanelOpenedSource) says + // which hook fired. On JetBrains "webview_initialized" also fires on every webview + // reload, e.g. after a core restart, so a crash-restart loop emits one per restart. PANEL_OPENED: "ui.panel_opened", // Tracks when the user explicitly starts a new task flow NEW_TASK_CLICKED: "ui.new_task_clicked", @@ -944,7 +953,7 @@ export class TelemetryService { }) } - public capturePanelOpened(source?: string) { + public capturePanelOpened(source: PanelOpenedSource) { this.capture({ event: TelemetryService.EVENTS.UI.PANEL_OPENED, properties: { source },