Skip to content

Commit 7dbdeb4

Browse files
committed
fix(tour): run guided-tour orchestration once on every boot
The Phoenix onboarding tour and the newly-added-features markdown were both wired up in ways that left users in non-default native projects silently un-onboarded. Two specific gaps: - new-project.js only called guidedTour.startTourIfNeeded() from closeDialogue() (fires when the new-project dialog is opened and then dismissed via openFolder) and from init() when the welcome screen pref was off. On a native app with a non-default project open, _shouldNotShowDialog() returned true and the dialog was never shown, so startTourIfNeeded() was never reached and the tour could not fire on any subsequent boot either. - newly-added-features.init() was called directly from main.js regardless of project context, so the "what's new" markdown could open under a user's current project tabs instead of the welcome project. Move all post-boot orchestration to guided-tour.js, fire it from a single _bootDonePromise.then() in new-project.js, and gate the "newly added features" surfacing on a real higher-version change (detected via semver vs the new userAlreadyDidAction.lastSeenAppVersion). On a higher version we switch to the welcome project first so the markdown lands in the onboarding context. Downgrades are a no-op and leave state untouched so a future re-upgrade to a previously-seen version does not re-fire. PhoenixTour stays a once-per-lifetime overlay tour gated by its own CURRENT_TOUR_VERSION constant.
1 parent 22cecc2 commit 7dbdeb4

3 files changed

Lines changed: 89 additions & 5 deletions

File tree

src/extensionsIntegrated/Phoenix/guided-tour.js

Lines changed: 77 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
// SPDX-License-Identifier: AGPL-3.0-only
22
// Copyright (c) 2021 - present core.ai. All rights reserved.
33

4-
/*global logger*/
4+
/*global logger, jsPromise*/
55

66
define(function (require, exports, module) {
77
const KernalModeTrust = window.KernalModeTrust;
@@ -21,9 +21,80 @@ define(function (require, exports, module) {
2121
Mustache = require("thirdparty/mustache/mustache"),
2222
SurveyTemplate = require("text!./html/survey-template.html"),
2323
PhoenixTour = require("./phoenix-tour"),
24+
newFeature = require("./newly-added-features"),
25+
ProjectManager = require("project/ProjectManager"),
26+
semver = require("thirdparty/semver.browser"),
2427
NOTIFICATION_BACKOFF = 10000,
2528
GUIDED_TOUR_LOCAL_STORAGE_KEY = "guidedTourActions";
2629

30+
function _currentAppVersion() {
31+
return window.AppConfig && window.AppConfig.apiVersion;
32+
}
33+
34+
function _isNewerVersion(v1, v2) {
35+
try {
36+
return semver.gt(v1, v2);
37+
} catch (e) {
38+
return false;
39+
}
40+
}
41+
42+
/**
43+
* Switch to the welcome project if we're not already there. Returns a
44+
* native Promise that resolves once the switch completes (or
45+
* immediately if no switch is needed). openProject returns a jQuery
46+
* deferred, so we adapt via jsPromise and swallow rejections so
47+
* downstream onboarding still runs against whatever project ended up
48+
* open.
49+
*/
50+
function _switchToWelcomeProject() {
51+
const welcomePath = ProjectManager.getWelcomeProjectPath();
52+
const currentPath = ProjectManager.getProjectRoot().fullPath;
53+
if (currentPath === welcomePath) {
54+
return Promise.resolve();
55+
}
56+
console.log("Guided tour: switching to welcome project for onboarding");
57+
return jsPromise(ProjectManager.openProject(welcomePath)).catch(function () { });
58+
}
59+
60+
/**
61+
* If the app was bumped to a newer version since we last recorded a
62+
* boot, switch to the welcome project and surface the "newly added
63+
* features" markdown there (the tour's intended onboarding context).
64+
* - First time we see this user: just record current and return; the
65+
* one-shot PhoenixTour handles fresh-user onboarding.
66+
* - Same version as recorded: no-op.
67+
* - Newer version than recorded: switch project + run newFeature.init.
68+
* - Lower version (downgrade): no-op, leave state untouched so a
69+
* future re-upgrade to the recorded version doesn't re-fire.
70+
*/
71+
function _maybeShowNewFeaturesForVersionChange() {
72+
const current = _currentAppVersion();
73+
if (!current) {
74+
return;
75+
}
76+
const lastSeen = userAlreadyDidAction.lastSeenAppVersion;
77+
if (lastSeen === current) {
78+
return;
79+
}
80+
if (!lastSeen) {
81+
// First time we're recording — no prior to compare against.
82+
userAlreadyDidAction.lastSeenAppVersion = current;
83+
PhStore.setItem(GUIDED_TOUR_LOCAL_STORAGE_KEY, JSON.stringify(userAlreadyDidAction));
84+
return;
85+
}
86+
if (!_isNewerVersion(current, lastSeen)) {
87+
// Downgrade — don't refresh, don't update state, so a
88+
// future re-upgrade to lastSeen doesn't fire again.
89+
return;
90+
}
91+
_switchToWelcomeProject().then(function () {
92+
newFeature.init();
93+
userAlreadyDidAction.lastSeenAppVersion = current;
94+
PhStore.setItem(GUIDED_TOUR_LOCAL_STORAGE_KEY, JSON.stringify(userAlreadyDidAction));
95+
});
96+
}
97+
2798
const surveyLinksURL = "https://updates.phcode.io/surveys.json";
2899

29100
// All popup notifications will show immediately on boot, we don't want to interrupt user amidst his work
@@ -280,6 +351,11 @@ define(function (require, exports, module) {
280351
tourStarted = true;
281352
_showBeautifyNotification();
282353
_showSurveys();
354+
// PhoenixTour is a once-per-lifetime overlay tour with its own
355+
// internal _shouldRun gate; safe to call every boot.
283356
PhoenixTour.startTour();
357+
// On a higher-version change, land on the welcome project and
358+
// surface the newly-added-features markdown.
359+
_maybeShowNewFeaturesForVersionChange();
284360
};
285361
});

src/extensionsIntegrated/Phoenix/main.js

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,6 @@ define(function (require, exports, module) {
2626
const serverSync = require("./serverSync"),
2727
newProject = require("./new-project"),
2828
defaultProjects = require("./default-projects"),
29-
newFeature = require("./newly-added-features"),
3029
AppInit = require("utils/AppInit"),
3130
Strings = require("strings"),
3231
Dialogs = require("widgets/Dialogs"),
@@ -95,7 +94,6 @@ define(function (require, exports, module) {
9594
serverSync.init();
9695
defaultProjects.init();
9796
newProject.init();
98-
newFeature.init();
9997
_detectUnSupportedBrowser();
10098
_persistBrowserStorage();
10199
});

src/extensionsIntegrated/Phoenix/new-project.js

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,6 @@ define(function (require, exports, module) {
101101
Metrics.countEvent(Metrics.EVENT_TYPE.NEW_PROJECT, "dialogue", "close");
102102
newProjectDialogueObj.close();
103103
exports.trigger(exports.EVENT_NEW_PROJECT_DIALOGUE_CLOSED);
104-
guidedTour.startTourIfNeeded();
105104
}
106105

107106
function showErrorDialogue(title, message) {
@@ -149,6 +148,18 @@ define(function (require, exports, module) {
149148
let _bootDoneDeferred = new $.Deferred();
150149
let _bootDonePromise = jsPromise(_bootDoneDeferred.promise());
151150

151+
// Fire the guided tour once on every boot, regardless of how the
152+
// new-project dialog flow resolved. Previously this was called from
153+
// a few specific paths (closeDialogue, init when welcome screen
154+
// disabled), which left users in non-default projects on native app
155+
// never seeing the tour. The individual notifications inside
156+
// startTourIfNeeded (beautify hint, surveys, one-shot PhoenixTour
157+
// overlay, newly-added-features markdown on version bump) all have
158+
// their own internal gating so it's safe to call every boot.
159+
_bootDonePromise.then(function () {
160+
guidedTour.startTourIfNeeded();
161+
});
162+
152163
function onBootComplete() {
153164
return _bootDonePromise;
154165
}
@@ -158,7 +169,6 @@ define(function (require, exports, module) {
158169
const shouldShowWelcome = PhStore.getItem("new-project.showWelcomeScreen") || 'Y';
159170
if(shouldShowWelcome !== 'Y') {
160171
Metrics.countEvent(Metrics.EVENT_TYPE.NEW_PROJECT, "dialogue", "disabled");
161-
guidedTour.startTourIfNeeded();
162172
_bootDoneDeferred.resolve();
163173
return;
164174
}

0 commit comments

Comments
 (0)