Skip to content

Commit 30be56b

Browse files
committed
feat(phoenix-tour): track ripple-target clicks and auto-peek AI panel
Adds two pieces of instrumentation/UX to the onboarding tour: - Per-step click metrics. Every step's highlighted target now has a one-shot capture-phase listener that fires stepN_clicked when the user actually clicks the ripple target during the overlay session. Detaches automatically on first click, on step transitions, and on teardown so we never double-count. - Step 2 AI-panel auto-peek. When step 2 begins, briefly switch the sidebar to the AI tab for 2 seconds and then revert to whatever tab the user was on. Throttled and cleaned up on transitions/teardown so the sidebar can't get stranded on AI if the tour ends mid-peek.
1 parent 59d06f7 commit 30be56b

1 file changed

Lines changed: 96 additions & 0 deletions

File tree

src/extensionsIntegrated/Phoenix/phoenix-tour.js

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ define(function (require, exports, module) {
3333
StringUtils = require("utils/StringUtils"),
3434
Metrics = require("utils/Metrics"),
3535
SidebarView = require("project/SidebarView"),
36+
SidebarTabs = require("view/SidebarTabs"),
3637
ProjectManager = require("project/ProjectManager"),
3738
EditorManager = require("editor/EditorManager"),
3839
CommandManager = require("command/CommandManager"),
@@ -76,6 +77,21 @@ define(function (require, exports, module) {
7677
let _rafId = null;
7778
let _timers = [];
7879

80+
// Per-step: tracks a click on the highlighted target while the
81+
// overlay is showing, fires a metric, then detaches. Cleared by
82+
// _detachStepClickMetric on step transitions and teardown.
83+
let _activeStepClickHandler = null;
84+
let _activeStepClickTarget = null;
85+
86+
// Step 2: when the step starts we briefly switch the sidebar to the
87+
// AI tab as an automatic peek (2s) and revert to whatever the user
88+
// was on. _peekPrevTab is non-null only while a peek is in flight so
89+
// teardown can revert cleanly if the tour ends mid-peek.
90+
let _step2PeekTimer = null;
91+
let _step2PeekPrevTab = null;
92+
const STEP2_PEEK_HOLD_MS = 2000;
93+
const SIDEBAR_AI_TAB_ID = "ai";
94+
7995
function _markComplete() {
8096
_state.version = CURRENT_TOUR_VERSION;
8197
_saveState(_state);
@@ -92,8 +108,74 @@ define(function (require, exports, module) {
92108
}
93109
}
94110

111+
/**
112+
* Attach a one-shot click listener to `$target` that fires a "stepN_clicked"
113+
* metric. Captures real user clicks during the overlay session — not the
114+
* synthetic class toggles the demos do. Replaces any previously attached
115+
* step handler so we never double-count across step transitions.
116+
*/
117+
function _attachStepClickMetric(stepNum, $target) {
118+
_detachStepClickMetric();
119+
if (!$target || !$target.length || !$target[0]) {
120+
return;
121+
}
122+
const targetEl = $target[0];
123+
const handler = function () {
124+
Metrics.countEvent(Metrics.EVENT_TYPE.GUIDE, "tour", "step" + stepNum + "_clicked");
125+
_detachStepClickMetric();
126+
};
127+
targetEl.addEventListener("click", handler, true);
128+
_activeStepClickTarget = targetEl;
129+
_activeStepClickHandler = handler;
130+
}
131+
132+
function _detachStepClickMetric() {
133+
if (_activeStepClickTarget && _activeStepClickHandler) {
134+
_activeStepClickTarget.removeEventListener("click", _activeStepClickHandler, true);
135+
}
136+
_activeStepClickTarget = null;
137+
_activeStepClickHandler = null;
138+
}
139+
140+
/**
141+
* Step 2 only: automatically switch the sidebar to the AI tab for a
142+
* couple of seconds so the user sees what's behind the tab, then
143+
* revert. No-op if the user is already on the AI tab.
144+
*/
145+
function _runStep2AIPeek() {
146+
_cancelStep2AIPeek();
147+
const current = SidebarTabs.getActiveTab && SidebarTabs.getActiveTab();
148+
if (current === SIDEBAR_AI_TAB_ID) {
149+
return;
150+
}
151+
_step2PeekPrevTab = current;
152+
SidebarTabs.setActiveTab(SIDEBAR_AI_TAB_ID);
153+
_step2PeekTimer = setTimeout(function () {
154+
if (_step2PeekPrevTab) {
155+
SidebarTabs.setActiveTab(_step2PeekPrevTab);
156+
}
157+
_step2PeekPrevTab = null;
158+
_step2PeekTimer = null;
159+
}, STEP2_PEEK_HOLD_MS);
160+
}
161+
162+
function _cancelStep2AIPeek() {
163+
if (_step2PeekTimer) {
164+
clearTimeout(_step2PeekTimer);
165+
_step2PeekTimer = null;
166+
}
167+
// If we tore down or transitioned mid-peek, restore the previous
168+
// tab so the sidebar doesn't get stranded on AI.
169+
if (_step2PeekPrevTab) {
170+
SidebarTabs.setActiveTab(_step2PeekPrevTab);
171+
_step2PeekPrevTab = null;
172+
}
173+
}
174+
95175
function _teardown() {
96176
_clearTimers();
177+
_detachStepClickMetric();
178+
_cancelStep2AIPeek();
97179
if ($overlay) {
98180
$overlay.remove();
99181
$overlay = null;
@@ -213,6 +295,7 @@ define(function (require, exports, module) {
213295
_trackTarget($btn, "right");
214296
_setStep(1);
215297
Metrics.countEvent(Metrics.EVENT_TYPE.GUIDE, "tour", "step1");
298+
_attachStepClickMetric(1, $btn);
216299
// Single, stable message for the entire step. The visible toggle of
217300
// design mode does the explaining; rotating text under a 2-second
218301
// demo is too quick to read.
@@ -248,6 +331,9 @@ define(function (require, exports, module) {
248331
}
249332

250333
function _runStep2() {
334+
// Each step transition cancels the previous step's instrumentation.
335+
_detachStepClickMetric();
336+
_cancelStep2AIPeek();
251337
_ensureSidebarVisible();
252338
const $tab = $('.sidebar-tab[data-tab-id="ai"]');
253339
if (!$tab.length) {
@@ -269,11 +355,17 @@ define(function (require, exports, module) {
269355
}
270356
}
271357
]);
358+
_attachStepClickMetric(2, $tab);
359+
// Auto-peek the AI panel for a couple of seconds so the user gets
360+
// a glance at its contents, then revert.
361+
_runStep2AIPeek();
272362
// Intentionally do NOT advance on a real click of the target — the
273363
// user needs time to read the prompt; only the Next button advances.
274364
}
275365

276366
function _runStep3() {
367+
_detachStepClickMetric();
368+
_cancelStep2AIPeek();
277369
_ensureSidebarVisible();
278370
const $newBtn = $("#newProject");
279371
if (!$newBtn.length) {
@@ -286,6 +378,7 @@ define(function (require, exports, module) {
286378
_trackTarget($newBtn, "right");
287379
_setStep(3);
288380
Metrics.countEvent(Metrics.EVENT_TYPE.GUIDE, "tour", "step3");
381+
_attachStepClickMetric(3, $newBtn);
289382
_setText(Strings.PHOENIX_TOUR_NEW_PROJECT);
290383
_setActions([
291384
{
@@ -354,6 +447,8 @@ define(function (require, exports, module) {
354447
}
355448

356449
async function _runStep4() {
450+
_detachStepClickMetric();
451+
_cancelStep2AIPeek();
357452
_ensureSidebarVisible();
358453
try {
359454
await _ensureLivePreviewReady();
@@ -375,6 +470,7 @@ define(function (require, exports, module) {
375470
_trackTarget($btn, "left");
376471
_setStep(4);
377472
Metrics.countEvent(Metrics.EVENT_TYPE.GUIDE, "tour", "step4");
473+
_attachStepClickMetric(4, $btn);
378474
_setText(Strings.PHOENIX_TOUR_EDIT_MODE);
379475
_setActions([
380476
{

0 commit comments

Comments
 (0)