From 062335e64fe5caa68fbf3dca6ceef856601a6645 Mon Sep 17 00:00:00 2001 From: betegon Date: Fri, 4 Sep 2026 17:36:57 +0200 Subject: [PATCH] feat(init): track completion-screen actions in run telemetry MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The completion screen lets users open Sentry (issues / first event) or install the Sentry agent plugin, but neither click emitted any telemetry, so we had no idea how many people take either action. Tag the run's `cli.command` transaction on interaction: - `wizard.completion.opened_sentry` — opened issues/first event - `wizard.completion.agent_plugin_clicked` — chose to install the plugin - `wizard.completion.agent_plugin_installed` — installer actually ran on exit Tags are bound in InkUI (main bundle) via a new `track` action so the Ink sidecar never imports the Sentry SDK, matching the existing `openUrl` pattern. Co-Authored-By: Claude Opus 4.8 --- packages/cli/src/lib/init/ui/ink-app.tsx | 4 +++ packages/cli/src/lib/init/ui/ink-ui.ts | 31 ++++++++++++++++++- packages/cli/src/lib/init/ui/types.ts | 19 +++++++++++- .../lib/init/ui/ink-app.snapshot.test.tsx | 3 ++ 4 files changed, 55 insertions(+), 2 deletions(-) diff --git a/packages/cli/src/lib/init/ui/ink-app.tsx b/packages/cli/src/lib/init/ui/ink-app.tsx index ec76e5e4f..07edafd50 100644 --- a/packages/cli/src/lib/init/ui/ink-app.tsx +++ b/packages/cli/src/lib/init/ui/ink-app.tsx @@ -735,6 +735,7 @@ function CompletionScreen({ case "issues": if (primaryUrl) { actions.openUrl(primaryUrl); + actions.track("open-sentry"); setNote("Opened Sentry in your browser."); } break; @@ -742,9 +743,11 @@ function CompletionScreen({ if (agentCommand) { if (agentQueued) { store.dequeuePostExitAction(agentCommand); + actions.track("agent-plugin-unqueued"); setNote(""); } else { store.queuePostExitAction(agentCommand); + actions.track("agent-plugin-queued"); setNote(`Queued — ${agentCommand} runs when you finish.`); } } @@ -773,6 +776,7 @@ function CompletionScreen({ !key.ctrl && (input === "o" || input === "O"), run: () => { actions.openUrl(primaryUrl); + actions.track("open-sentry"); setNote("Opened Sentry in your browser."); }, }, diff --git a/packages/cli/src/lib/init/ui/ink-ui.ts b/packages/cli/src/lib/init/ui/ink-ui.ts index 1a57b6dd6..4bc315786 100644 --- a/packages/cli/src/lib/init/ui/ink-ui.ts +++ b/packages/cli/src/lib/init/ui/ink-ui.ts @@ -53,7 +53,7 @@ import { ReadStream } from "node:tty"; const _require = createRequire(import.meta.url); -import { setTag } from "@sentry/node-core/light"; +import { addBreadcrumb, setTag } from "@sentry/node-core/light"; import { FULL_BANNER_LINES } from "../../banner.js"; import { openBrowser } from "../../browser.js"; import { CLI_VERSION } from "../../constants.js"; @@ -538,6 +538,29 @@ export class InkUI implements WizardUI { // ignore }); }, + track: (event) => { + // Land completion-screen interactions on the run's `cli.command` + // transaction so we can see, per run, whether the user opened Sentry + // and/or chose to install the agent plugin. Tags are sticky: once a + // user has clicked "install", un-toggling leaves the click recorded. + switch (event) { + case "open-sentry": + setTag("wizard.completion.opened_sentry", "true"); + break; + case "agent-plugin-queued": + setTag("wizard.completion.agent_plugin_clicked", "true"); + break; + case "agent-plugin-unqueued": + break; + default: + break; + } + addBreadcrumb({ + category: "wizard.completion", + message: event, + level: "info", + }); + }, }, }); } @@ -884,6 +907,12 @@ export class InkUI implements WizardUI { */ private async runPostExitActions(): Promise { const actions = this.store.getSnapshot().postExitActions; + if (actions.length > 0) { + // The agent-plugin installer is the only thing the completion screen + // queues, so a non-empty list here means the user finished with it queued + // and we're about to actually run it — the strongest "installed" signal. + setTag("wizard.completion.agent_plugin_installed", "true"); + } for (const command of actions) { process.stdout.write(`\n$ ${command}\n`); await runInheritedCommand(command); diff --git a/packages/cli/src/lib/init/ui/types.ts b/packages/cli/src/lib/init/ui/types.ts index 9e4df7d05..1b762346d 100644 --- a/packages/cli/src/lib/init/ui/types.ts +++ b/packages/cli/src/lib/init/ui/types.ts @@ -219,14 +219,31 @@ export type WizardCompletion = { startCommand?: string; }; +/** + * A completion-screen interaction worth recording on the run's telemetry. + * `open-sentry` covers both the "View my first event" / "Open my Issues feed" + * menu item and the `o` shortcut; the agent-plugin events track the install + * toggle (a queued click is the "did they choose to install the plugin" signal). + */ +export type CompletionActionEvent = + | "open-sentry" + | "agent-plugin-queued" + | "agent-plugin-unqueued"; + /** * Side-effectful actions the completion screen invokes. Provided by `InkUI` * (which lives in the main bundle) so the pre-bundled Ink sidecar never has to - * import Node built-ins (browser launch) itself. + * import Node built-ins (browser launch) or the Sentry SDK itself. */ export type CompletionActions = { /** Open a URL in the user's browser. Best-effort, non-blocking. */ openUrl: (url: string) => void; + /** + * Record a completion-screen interaction on the run's telemetry. Bound by + * `InkUI` so the Ink sidecar never imports Sentry; the tags land on the + * active `cli.command` transaction like the rest of the `wizard.*` tags. + */ + track: (event: CompletionActionEvent) => void; }; /** diff --git a/packages/cli/test/lib/init/ui/ink-app.snapshot.test.tsx b/packages/cli/test/lib/init/ui/ink-app.snapshot.test.tsx index ca9b64d80..964477d1e 100644 --- a/packages/cli/test/lib/init/ui/ink-app.snapshot.test.tsx +++ b/packages/cli/test/lib/init/ui/ink-app.snapshot.test.tsx @@ -1329,6 +1329,9 @@ describe("completion screen", () => { openUrl: () => { // no-op in tests }, + track: () => { + // no-op in tests + }, }, }, });